树的直径
树的直径
给定一棵树,树中每条边都有一个权值,树中两点之间的距离定义为连接两点的路径边权之和。树中最远的两个节点之间的距离被称为树的直径,连接这两点的路径被称为树的最长链。后者通常也可称为直径,即直径是一个数值概念,也可代指一条路径
树的直径通常有两种求法,时间复杂度均为 O(n) 。我们假设树以 N 个点 N-1 条边的无向图形式给出,并存储在邻接表中。
解法1:dp
状态:设d[x]表示从节点x出发走向以x为根的子树,能够到达的最远节点的距离。
我们枚举每一个结点 x 以及 它要到达的下一个结点 Eiv。
这两个结点所能达到的最大距离之和加上这两个结点的边权就有可能去更新树的直径。
即:树的直径 $ans = max(ans, d[ x ] + d[ Eiv ] + edge[x, Eiv] ), (1 <= x <= N) $
那么 d[ x ] 通过什么更新呢?当然是由 它所连接下一个结点所能达到最大距离来更新了;
即 $d[ x ] = max( d[ x ], d[ Eiv ] + edge[ x, Eiv ] )$;
1 void dp(int st) 2 { 3 vis[st] = true; //当前结点已访问 4 for(int i = head[st]; i != -1; i = e[i].next){ 5 int Eiv = e[i].v; 6 if(vis[Eiv]) continue; //不走回头路 7 dp(Eiv); 8 ans = max(ans, d[st] + d[Eiv] + e[i].w); //更新树的直径(由当前结点两段之和更新) 9 d[st] = max(d[st], d[Eiv]+e[i].w); //更新当前结点所能走的最长路径(保留较长的那边) 10 } 11 }
解法2:两次BFS(DFS)求树的直径
通过两次BFS或者两次DFS也可以求树的直径,并且更容易计算出直径上的具体节点
详细地说,这个做法包含两步:
1.从任意节点出发,通过BFS和DFS对树进行一次遍历,求出与出发点距离最远的节点记为p
2.从节点p出发,通过BFS或DFS再进行一次遍历,求出与p距离最远的节点,记为q。
从p到q的路径就是树的一条直径。因为p一定是直径的一端,否则总能找到一条更长的链,与直径的定义矛盾。显然地脑洞一下即可。p为直径的一端,那么自然的,与p最远的q就是直径的另一端。
在第2步的遍历中,可以记录下来每个点第一次被访问的前驱节点。最后从q递归到p,即可得到直径的具体方案
1 void dfs(int s) 2 { 3 for(int i = head[s]; i != -1; i = edge[i].nxt){ 4 int Eiv = edge[i].v; 5 if(fa[s] == Eiv) continue; //不走回头路,也可以递归父亲结点省去fa数组空间 6 fa[Eiv] = s; 7 dis[Eiv] = dis[s] + edge[i].w; //当前结点最长路径 8 dfs(Eiv); 9 } 10 }