树形结构 —— 树与二叉树 —— 树的直径
【定义】
给定一棵树,树中的每条边都有一个权值。
- 树中两点的距离:连接两点的路径边权之和
- 树的直径:树中最远的两个节点之间的距离
- 树的最长链:连接树中最远的两个结点的路径
【实现】
树的直径通常有两种求法,时间复杂度均为O(n)。
假设树以 n 个点 n-1 条边的无向图形式给出,存储在邻接表中。
1.两次DFS求树的直径
通过两次 dfs 可以求树的直径,且更容易计算出直径上的具体结点
- 从任意结点出发,通过 dfs 对树进行一次遍历,求出于出发点距离最远的结点,记为 st
- 从结点 st 出发,通过 dfs 对树再进行一次遍历,求出于 st 距离最远的结点,记为 ed
则:st 到 ed 的路径就是树的直径
在第 2 步的遍历中,可以记录下来每个点第一次被访问的前驱节点,最后从 q 递归到 p,即可得到直径的具体方案
struct Edge {
int to, val;
int next;
Edge(){}
Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int dis[N], maxx, id;
int st, ed, diameter;//起点、终点、直径
int disSt[N], disEd[N];//起点、终点分别到每个点的距离
void addEdge(int from, int to, int val) {
edge[++tot].to = to;
edge[tot].val = val;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int x, int father) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int y = edge[i].to;
int val = edge[i].val;
if (y == father)
continue;
dis[y] = dis[x] + val;
if(dis[y]>maxx){
maxx = dis[y];
id = y;
}
dfs(y, x);
}
}
void calcDiameter(){
//第一遍dfs
maxx = 0;
id = 1;
dfs(1, 0);
st = id;
//第二遍dfs
maxx = 0;
dis[st] = 0;
dfs(st, 0);
ed = id;
diameter = maxx; //树的直径
for (int i = 1; i <= n; i++)
disSt[i] = dis[i];
dis[ed] = 0;
dfs(ed, 0);
for (int i = 1; i <= n; i++)
disEd[i] = dis[i];
}
int main() {
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1; i <= n - 1; i++) {
int x, y, val;
scanf("%d%d%d", &x, &y, &val);
addEdge(x, y, val);
addEdge(y, x, val);
}
calcDiameter();
printf("%d %d\n", st, ed);
printf("%d\n", diameter);
return 0;
}
2.树形DP求树的直径
设 1 号节点为根,n 个点 n-1 条边的无向图就可以看做有根树
设 dis1[x] 表示从点 x 到以 x 为根的子树中叶结点的最长链, pos1[x] 表示 dis1[x] 在哪个点更新;dis2[x] 表示从点 x 到以 x 为根的子树中叶结点的次长链,pos2[x] 表示 dis2[x] 在哪个点更新,且要求两条链不能有交集。
这样一来,对于任意一点 i,经过点 i 的最长链的分为两个部分:i 的最长链 dis1[i]、i 的次长链 dis2[i]
那么,整棵树的直径就是
struct Edge {
int to, val;
int next;
Edge(){}
Edge(int to,int val,int next):to(to),val(val),next(next){}
} edge[N];
int n;
int head[N], tot;
int dis1[N], dis2[N];//分别维护第i个点的最长链、次长链
int pos1[N],pos2[N];//分别维护dis1[i]、dis2[i]从哪个点更新
void addEdge(int from, int to, int val) {
edge[++tot].to = to;
edge[tot].val = val;
edge[tot].next = head[from];
head[from] = tot;
}
void dfs(int x, int father) {
for (int i = head[x]; i != -1; i = edge[i].next) {
int y = edge[i].to;
int val = edge[i].val;
if (y == father)
continue;
dfs(y, x);
if (dis1[y] + val > dis1[x]) {
dis2[x] = dis1[x];
dis1[x] = dis1[y] + val;
pos2[x] = pos1[x];
pos1[x] = y;
}
else if (dis1[y] + val > dis2[x]) {
dis2[x] = dis1[y] + val;
pos2[x] = y;
}
}
}
int main() {
scanf("%d", &n);
memset(head, -1, sizeof(head));
for (int i = 1; i <= n - 1; i++) {
int x, y, val;
scanf("%d%d%d", &x, &y, &val);
addEdge(x, y, val);
addEdge(y, x, val);
}
dfs(1, 0);
int diameter = -INF;
for (int i = 1; i <= n; i++)
diameter = max(dis1[i] + dis2[i], diameter);
printf("%d", diameter);
return 0;
}