B4016 树的直径 c++题解
免责说明:本题思路来自我的学长Yzh929,感谢学长提供的宝贵思路
题目链接
B4016 树的直径
题目描述
给定一棵 n n n 个结点的树,树没有边权。请求出树的直径是多少,即树上最长的不重复经过一个点的路径长度是多少。
输入格式
第一行输入一个正整数 n n n,表示结点个数。
第二行开始,往下一共 n − 1 n-1 n−1 行,每一行两个正整数 ( u , v ) (u,v) (u,v),表示一条边。
输出格式
输出一行,表示树的直径是多少。
输入输出样例 #1
输入 #1
5 1 2 2 4 4 5 2 3输出 #1
3说明/提示
数据保证, 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105。
1.题目内容
定义为树中两个节点之间最长 简单路径(无重复节点)的长度,也称最长链。
输入一棵树(无向、无环、连通图),输出其直径值。
2.难度
- 普及/提高−
3.题目所需知识点
- 深度优先搜索 DFS
- 树形 DP
- 树的直径
4.知识点讲解
树上任意两节点之间最长的简单路径即为树的「直径」。
如图所示:

5. 思路(本题采用深度优先搜索 DFS解)
算法核心思想:
-
步骤1:从任意节点(如节点1)出发,通过DFS找到最远节点 begin(必为直径的一个端点)。
-
步骤2:从 begin 出发再次DFS,找到最远节点 end,此时 begin 到 end 的路径即为直径。
关键数据结构:
- vector tr[]:邻接表存储树结构,空间复杂度O(n)。
- dep[]数组:记录节点深度,用于计算路径长度。
- maxx和node:动态维护当前最大深度及对应节点。
注:树的直径——可能有多个
6.复杂度分析
A 时间复杂度分析
两次DFS遍历:
-
代码通过两次DFS求解树的直径,每次DFS均完整遍历所有节点(共 n 个),时间复杂度为 O(n)。
-
总时间复杂度为 O(n) + O(n) = O(n),即线性复杂度,效率最优。
邻接表操作:
- 使用 vector 存储树结构时,每条边仅被访问一次(共 n-1 条边),建图时间复杂度为 O(n)。
B 空间复杂度分析
邻接表存储:
- 邻接表 tr[] 存储 n 个节点的邻接关系,空间复杂度为 O(n)。
辅助数组:
- 深度数组 dep[] 占用 O(n) 空间,无额外大规模开销。
- 总空间复杂度为 O(n),与节点规模线性相关。
| 维度 | 复杂度 | 优势 |
|---|---|---|
| 时间 | O(n) | 两次DFS严格线性遍历,无冗余操作 |
| 空间 | O(n) | 仅需邻接表+深度数组,无递归栈溢出风险 |
| 适用性 | - | 高效处理 1e5 节点规模 的数据 |
7.代码片段分析
学长讲解
//众所周知,树的直径简单来说即是树上边权和最大的路径 //ta的查找方法是: // //1.先从任意一点出发,找到离这个点最远的地方 ,记为begin // 2.从 begin出发,找到离 begin最远的点,记为end // 3.同时记录 begin—end的边权之和,即为答案
- 变量和vector的定义
int n,u,v,node,dep[(int)1e5+5],maxx=-1;
vector<int> tr[(int)1e5+5];//vector存图
- 深搜函数的定义
void dfs(int x,int f){//x 表示当前节点,f表示其父亲
for(auto ne:tr[x]){ //遍历tr[x]的儿子,ne表示遍历到的tr[x]的每一个儿子
if(ne==f) continue;//不能往回走(儿子不能走向父亲,不然死循环)
dep[ne]=dep[x]+1;//递推更新儿子节点深度
dfs(ne,x);//dfs
}
if(dep[x]>maxx){//找最远距离
maxx=dep[x];
node=x;//找最远节点
}
}
- vector存双向边
for(int i=1;i<=n-1;i++){
cin>>u>>v;
tr[u].push_back(v); //vector存双向边
tr[v].push_back(u);
}
树是一种特殊的无向连通无环图,每条边实际是双向连接的。当存储边(u,v)时,需要在u的邻接表中添加v,同时在v的邻接表中添加u,这样才能完整表示无向边。
双向存储确保DFS能正确遍历所有邻接节点。例如代码中 if(ne==f) continue 通过父节点判断避免回溯,依赖的就是双向存储提供的完整邻接关系。
- 第一次dfs和第二次dfs的使用
dfs(1,0); //第一遍dfs完成步骤1
memset(dep,0,sizeof(dep));//注意清空统计需要用的量
maxx=-1;
dfs(node,0);//dfs同理,完成步骤2
- 输出
cout<<dep[node]<<'\n';//由于第二遍dfs是从begin为根开始的,因此end的深度即为所求
cout<<dep[node]<<'\n';//由于第二遍dfs是从begin为根开始的,因此end的深度即为所求
return 0;
}
//学长现敲,完结撒花 ~
再次感谢学长提供的思路
8.AC代码
//众所周知,树的直径简单来说即是树上边权和最大的路径
//ta的查找方法是:
// 1.先从任意一点出发,找到离这个点最远的地方 ,记为begin
// 2.从 begin出发,找到离 begin最远的点,记为end
// 3.同时记录 begin—end的边权之和,即为答案
#include<bits/stdc++.h>
using namespace std;
int n,u,v,node,dep[(int)1e5+5],maxx=-1;
vector<int> tr[(int)1e5+5];//vector存图
void dfs(int x,int f){//x 表示当前节点,f表示其父亲
for(auto ne:tr[x]){ //遍历tr[x]的儿子,ne表示遍历到的tr[x]的每一个儿子
if(ne==f) continue;//不能往回走(儿子不能走向父亲,不然死循环)
dep[ne]=dep[x]+1;//递推更新儿子节点深度
dfs(ne,x);//dfs
}
if(dep[x]>maxx){//找最远距离
maxx=dep[x];
node=x;//找最远节点
}
}
int main(){
cin>>n;
for(int i=1;i<=n-1;i++){
cin>>u>>v;
tr[u].push_back(v); //vector存双向边
tr[v].push_back(u);
}
dfs(1,0); //第一遍dfs完成步骤1
memset(dep,0,sizeof(dep));//注意清空统计需要用的量
maxx=-1;
dfs(node,0);//dfs同理,完成步骤2
cout<<dep[node]<<'\n';//由于第二遍dfs是从begin为根开始的,因此end的深度即为所求
return 0;
}
//学长现敲,完结撒花 ~
9.推荐题目
如果对你理解有帮助,就点个赞再走吧,有问题欢迎随时在评论区指出
189

被折叠的 条评论
为什么被折叠?



