树的直径算法

树上任意两节点之间最长的简单路径即为树的「直径」。记 δ(s,t) 为树的真实直径。

算法1,两遍dfs

首先从任意节点 y 开始进行第一次 DFS,到达距离其最远的节点,记为 z,然后再从 z 开始做第二次 DFS,到达距离 z 最远的节点,记为 z,则 即为树的直径。

那么为什么此算法可行?

我们只需证明对于任意一点 x 经过第一次dfs之后到达的点 y 一定是 δ(s,t) 的一端 st

定理:在一棵树上,从任意节点 x 开始进行一次 DFS,到达的距离其最远的节点 z 必为直径的一端。

我们采用反证法。记出发点为 y,真实直径 δ(s,t),设从 y 进行的第一次dfs到达的距离其最远的点 z 不为 ts
那么有三种情况:

  • yδ(s,t) 上:那么 δ(y,z)>δ(y,t)δ(x,z)>δ(x,t)δ(s,z)>δ(s,t) 矛盾。其中 xδ(s,t) 上一中转点
    image
  • y 不在直径上,且 δ(y,z)δ(s,t) 有重合路径
    image
    那么 δ(y,z)>δ(y,t)δ(x,z)>δ(x,t)δ(s,z)>δ(s,,t) 矛盾。
  • y 不在 δ(s,t) 上,且 δ(y,z)δ(s,t) 不存在重复路径:
    image
    那么 δ(y,z)>δ(y,t)δ(x,z)>δ(x,t)δ(x,z)>δ(x,t)δ(s,z)>δ(s,t)矛盾。
  • 三种情况都矛盾,得证。

注意负权边不适用

那么代码实现就很简单了

const int N = 10005;

int n, c, d[N];
vector<int> e[N];

void dfs(int u, int fa) {
  for (int v :e[u]) {
    if (v == fa) continue;
    d[v] = d[u] + 1;
    if (d[v] > d[c]) c = v;
    dfs(v, u);
  }
}

int main() {
  scanf("%d", &n);
  for (int i = 1; i < n; i++) {
    int u, v;
    scanf("%d %d", &u, &v);
    e[u].push_back(v), e[v].push_back(u);
  }
  dfs(1, 0);
  d[c] = 0, dfs(c, 0);
  printf("%d\n", d[c]);
  return 0;
}

O(N)?

树形dp

每个节点作为子树的根向下,所能延伸的最远距离 d1,和次远距离 d2,那么直径就是所有 d1+d2 的最大值。

树形 DP 可以在存在负权边的情况下求解出树的直径。

const int N=10010,M=20010;
int n,a,b,c,ans;
struct edge{int v,w;};
vector<edge> e[N];

int dfs(int x,int fa){
  int d1=0,d2=0; 
  for(auto ed : e[x]){
    int y=ed.v, z=ed.w;
    if(y==fa) continue;
    int d=dfs(y,x)+z;
    if(d>=d1) d2=d1,d1=d;
    else if(d>d2) d2=d;
    // printf("回%d d1=%d d2=%d\n",x,d1,d2);
  }
  ans=max(ans,d1+d2);
  // printf("离%d ans=%d\n",x,ans);
  return d1;
}
int main(){
  cin>>n;
  for(int i=1; i<n; i++){
    cin>>a>>b>>c;
    e[a].push_back({b,c});
    e[b].push_back({a,c});
  }
  dfs(1,-1);
  cout << ans << endl;
  return 0;
}

最后的最后,始终要记住,树是无环的,这一性质很重要,在两个算法中都用到了。

posted @   Vegdie  阅读(361)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示