树 类别 树可以简单分为有根树和无根树
有根树 除了根节点以外,每个节点都存在父节点
数据结构 1 2 3 4 5 vector<int > edges[N + 1 ];int father[N + 1 ];void addedge (int x, int y) { edges[x].push_back (y); }
DFS遍历 1 2 3 4 5 6 7 8 vector<int > dfn;void dfs (int x) { dfn.push_back (x); for (auto y : edges[x]) { dfs (y); } }dfs (root);
BFS遍历 1 2 3 4 5 6 7 8 9 10 11 12 int q[100 ], front = 1 , rear = 0 void bfs (int root) { q[++rear] = root; while (front <= rear) { int x = q[front]; ++front; for (auto y : edges[x]) { q[++rear] = y; } } }bfs (root);
无根树 不存在子节点和父节点,只存在相邻节点
DFS遍历 在进入一个新的节点时,需要记录其来源的节点,遍历在遍历相邻节点时,重复处理
1 2 3 4 5 6 7 void dfs (int from , int x) { for (auto y : edges[x]) { if (y != from) { dfs (x, y); } } }
BFS遍历 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int q[100 ], front = 1 , rear = 0 ;void bfs (int x) { q[++rear] = x; int from = -1 ; while (front <= rear) { int x = q[front], ++front; for (auto y : edges[x]) { if (y != from) { q[++rear] = y; } } } }
树的直径 树的直径是指树上任意两个节点之间最长的路径
树的直径的中间节点被称为树的中心。如果直径上有偶数个节点,则中间的两个节点都可以称为树的中心。
树的中心到其它点的最长路径最短
求解树的直径 树的直径可以用两次搜索求得,第一次从任意一个节点开始搜索,找出距离最远的节点a。第二次从a节点开始搜索,找到距离节点a最远的节点b,则路径a-b即为树的直径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int n, pre[1001 ], dist[1001 ]; vector<int > edges[1001 ];void dfs (int x) { for (auto y : edges[x]) { if (y != pre[x]) { pre[y] = x; dist[y] = dist[x] + 1 ; dfs (y); } } }int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++) { int x, y; scanf ("%d%d" , &x, &y); edges[x].push_back (y); edges[y].push_back (x); } memset (dist, 0 , sizeof (dist)); memset (pre, 0 , sizeof (pre)); pre[1 ] = -1 ; dfs (1 ); int idx = 0 , v = 0 ; for (int i = 1 ; i <= n; i++) { if (dist[i] > v) v = dist[i], idx = i; } memset (dist, 0 , sizeof (dist)); memset (pre, 0 , sizeof (pre)); pre[idx] = -1 ; dfs (idx); v = 0 ; for (int i = 1 ; i <= n; i++) { if (dist[i] > v) v = dist[i]; } printf ("%d" , v); }
树的重心 对于无根树来说,以任意节点为根节点,其子树大小中的最大值,最小的那个节点,就是树的重心
一棵树可能有1或2个重心
性质 当重心为根节点时,它底下每个子树的大小不大于整棵树大小的一半
重心到其它所有节点的距离和最小
计算树的重心 以某个节点为根节点,对整棵树进行一次遍历,在遍历的同时记录下每个节点子树的大小
在计算以其它节点为根节点x时,因为已经计算过一部分子树的大小,只是没有计算它的来源节点作为子节点时,子树的大小,此时可以直接通过n - size[x],即可得到来源节点子树的大小
最终每个节点作为根节点,子树的最大值,再从中找出最小的一个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 int n, pre[101 ], size[101 ];; vector<int > edges[101 ];void solve (int x) { size[x] = 1 ; for (auto y : edges[x]) { if (y != pre[x]) { pre[y] = x; solve (y); size[x] += size[y]; } } }int main () { scanf ("%d" , &n); for (int i = 1 ; i <= n; i++) { int x, y; scanf ("%d%d" , &x, &y); edges[x].push_back (y); edges[y].push_back (x); } memset (pre, 0 , sizeof (pre)); solve (1 ); int idx, v = 1 << 30 ; for (int i = 1 ; i <= n; i++) { int f = 0 ; for (auto y : edges[i]) { if (y != pre[x]) { f = max (f, size[y]); } else { f = max (f, n - size[x]); } } if (f < v) { v = f, idx = i; } } printf ("%d" , idx); }
树的最近公共祖先 先对整棵树进行遍历,获取到每一个节点到根节点的距离
使用倍增的思想,将距离远的那个节点跳到与另一个节点同一层级
再次使用倍增,两个节点同时向上跳,找到最近的公共祖先
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int n, dist[101 ], father[101 ][20 ]; vector<int > edges[101 ];void dfs (int x) { for (auto y : edges[x]) { dist[y] = dist[x] + 1 ; dfs (y); } }int main () { scanf ("%d" , &n); for (int i = 1 ; i < n; i++) { int x, y; scanf ("%d%d" , &x, &y); edges[x].push_back (y); father[y][0 ] = x; } for (int i = 1 ; i <= 20 ; i++) { for (int j = 1 ; j < n; j++) { if (father[j][i - 1 ]) { father[j][i] = father[father[j][i - 1 ]][i - 1 ]; } } } memset (dist, 0 , sizeof (dist)); dfs (1 ); scanf ("%d" , &m); for (int i = 1 ; i <= m; i++) { int x, y; scanf ("%d%d" , &x, &y); if (dist[x] < dist[y]) swap (x, y); int z = dist[x] - dist[y]; for (int j = 0 ; j <= 20 && z; j++, z /= 2 ) { if (z & 1 ) x = father[x][j]; } if (x == y) { printf ("%d\n" , x); continue ; } for (int j= 20 ; j >= 0 ; j--) { if (father[x][j] != father[y][j]) { x = father[x][j], y = father[y][j]; } } printf ("%d\n" , father[x][0 ]); } }