树形结构 —— 树与二叉树 —— 树的直径

【定义】

给定一棵树,树中的每条边都有一个权值。

  • 树中两点的距离:连接两点的路径边权之和
  • 树的直径:树中最远的两个节点之间的距离
  • 树的最长链:连接树中最远的两个结点的路径

【实现】

树的直径通常有两种求法,时间复杂度均为O(n)。

假设树以 n 个点 n-1 条边的无向图形式给出,存储在邻接表中。

1.两次DFS求树的直径

通过两次 dfs 可以求树的直径,且更容易计算出直径上的具体结点

  1. 从任意结点出发,通过 dfs 对树进行一次遍历,求出于出发点距离最远的结点,记为 st
  2. 从结点 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]

那么,整棵树的直径就是 diameter=max(diameter,dis1[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;
}

 

posted @ 2022-09-20 23:00  老程序员111  阅读(45)  评论(0编辑  收藏  举报