树的直径

树的直径:树上最远两点(叶子结点)的距离。那么怎么求树的直径呢?

  1. 方法一:暴力求解,从每个点开始遍历图,可以得到每个点v所在的最长路径max1和次长路径max2,注意的是最长路径和次长路径除了点v没有其他公共结点。如下图所示,经过点A的最长路径应该是下图左所示的路径,而非下图右所示的路径,通过A的最长路径必须是来自不同分支。方法一,暴力破解,每个点做一趟DFS的话,时间复杂度为 O ( n 2 ) O(n^2) O(n2)
    在这里插入图片描述
  2. 方法二:从树上任意点u开始DFS(BFS)遍历图,得到距离u最远的结点v,然后从v点开始DFS遍历图,得到距离v最远的结点w, 则v、w之间的距离就是树的直径。
    证明:假设路径v-w为树的直径
    1)u位于v-w所在的路径上,如下图左图所示,那么从u点做DFS能访问到的最远点必然是v或w, 否则假设访问到的最远点为x, 如下图所示,有 d i s t ( u , x ) ≥ d i s t ( u , v ) , d i s t ( u , x ) ≥ d i s t ( u , w ) dist(u,x) \geq dist(u,v), dist(u,x) \geq dist(u,w) dist(u,x)dist(u,v)dist(u,x)dist(u,w), 分两种情况讨论:
    a) 如果只取大于号, d i s t ( u , x ) > d i s t ( u , v ) , d i s t ( u , x ) > d i s t ( u , w ) dist(u,x) > dist(u,v) , dist(u,x) > dist(u,w) dist(u,x)>dist(u,v)dist(u,x)>dist(u,w)
    那么 d i s t ( u , x ) + d i s t ( u , v ) = d i s t ( v , x ) > d i s t ( u , v ) + d i s t ( u , w ) = d i s t ( v , w ) dist(u,x) + dist(u,v) = dist(v,x) > dist(u,v) + dist(u,w) = dist(v,w) dist(u,x)+dist(u,v)=dist(v,x)>dist(u,v)+dist(u,w)=dist(v,w), 那么v-w不是树的是直径,跟假设矛盾。
    b) 如果取大于等于号, d i s t ( u , x ) ≥ d i s t ( u , v ) , d i s t ( u , x ) ≥ d i s t ( u , w ) dist(u,x) \geq dist(u,v) , dist(u,x)\geq dist(u,w) dist(u,x)dist(u,v)dist(u,x)dist(u,w)
    假设 d i s t ( u , x ) = d i s t ( u , v ) dist(u,x) = dist(u,v) dist(u,x)=dist(u,v) 那么 d i s t ( x , w ) = d i s t ( v , w ) dist(x,w) = dist(v,w) dist(x,w)=dist(v,w), 这样也没问题,树的直径不唯一而已,那么x依然位于树的直径的一个端点上。
    2)u不位于v-w所在的路径上,如下图右图所示,那么有 d i s t ( u , x ) > d i s t ( u , y , v ) , d i s t ( u , x ) > d i s t ( u , y , w ) dist(u,x) > dist(u,y,v) , dist(u,x) > dist(u,y,w) dist(u,x)>dist(u,y,v)dist(u,x)>dist(u,y,w), 这里y是u到路径[v-w]的任意点,那么就有 d i s t ( u , x ) + d i s t ( u , y , w ) = d i s t [ x , w ] > d i s t ( v , y ) + d i s t ( y , w ) = d i s t ( v , w ) dist(u,x) + dist(u,y,w) = dist[x,w] > dist(v,y) + dist(y,w) = dist(v,w) dist(u,x)+dist(u,y,w)=dist[x,w]>dist(v,y)+dist(y,w)=dist(v,w), 那么说明那么v-w不是树的是直径,跟假设矛盾。
    综上,方法二正确,且复杂度为2趟DFS,因此复杂度为 O ( n ) O(n) O(n)。比方法一快很多。在这里插入图片描述
    模板如下:
#include <iostream>
#include <cstring>
using namespace std;

//maxv:源点能到的最远点,maxdis:最远点对应的距离, 
const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], maxdis,maxv, ne; 

void add(int u, int v, int w) {
	edges[ne] = { v, head[u], w };
	head[u] = ne++;
}

//u:dfs的源点,f: u点的父节点,d2s:u点到源点的距离
void dfs(int u, int f, int d2s) {
	if (maxdis < d2s){
		maxdis = d2s;
		maxv = u;
	}
	for (int e = head[u]; e != -1; e = edges[e].next) {
		int v = edges[e].to, w = edges[e].w;
		if (v == f) continue;  //父节点已经访问过,防止重复遍历,相反孩子不会重复遍历。
		dfs(v, u, d2s + w);
	}
}

int main() {
	int e, u, v, w, s;
	cin >> e;
	memset(head, -1, sizeof(head));
	for (int i = 1; i <= e; i++) {
		cin >> u >> v >> w;
		add(u, v, w), add(v, u, w);
	}
	dfs(1, -1, 0); //从结点1开始遍历,找到最远点maxv及对应的最远距离maxdis
	maxdis = 0;
	dfs(maxv, -1, 0);//从结点maxv开始遍历,找到最远点对应的距离maxdis
	cout << maxdis << endl;
	return 0;
}

重要性质:
树上任意点能到的最远点,一定是树的直径的某个端点。通过方法二的反证法可以推出此结论。
例题:HDOJ 2196:http://acm.hdu.edu.cn/showproblem.php?pid=2196
解法一:根据上述结论,先求出树的直径,然后从直径的两个端点u和v分别DFS整棵树,对于每个结点得到两个距离d[i].u和d[i].v, 二者的最大值即是i点能到的最远点的距离。

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], vis[maxn], d1[maxn], d2[maxn], maxv, n, ne;

void add(int u, int v, int w) {
	edges[ne] = { v, head[u], w };
	head[u] = ne++;
}

void dfs(int u, int f, int depth, int* d) {
	d[u] = depth;
	if (d[maxv] < d[u]) maxv = u;
	for (int e = head[u]; e != -1; e = edges[e].next) {
		int v = edges[e].to, w = edges[e].w;
		if (v == f) continue;
		dfs(v, u, depth + w, d);
	}
}

int main() {
	int n, u, v, w, s;
	while (~scanf("%d", &n)) {
		memset(head, -1, sizeof(head));
		ne = 0;
		for (int i = 2; i <= n; i++) {
			scanf("%d%d", &v, &w);
			add(i, v, w), add(v, i, w);
		}
		s = 1, d1[maxv] = 0;
		dfs(s, -1, 0, d1);
		s = maxv, d1[maxv] = 0;
		dfs(s, -1, 0, d1);
		s = maxv, d2[maxv] = 0;
		dfs(s, -1, 0, d2);

		for (int i = 1; i <= n; i++)
			printf("%d\n", max(d1[i], d2[i]));
	}
	return 0;
}

解法二:树上DP,讲解参见:
1)https://www.cnblogs.com/WABoss/p/5267488.html?tdsourcetag=s_pcqq_aiomsg
2)https://blog.csdn.net/shuangde800/article/details/9732825

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;

const int maxn = 1e4 + 5;
struct Edge { int to, next, w; }edges[2 * maxn];
int head[maxn], dis[maxn][3], maxson[maxn], vis[maxn], n, tot;

void add(int u, int v, int w) {
    edges[tot] = { v, head[u], w };
    head[u] = tot++;
}
void dfs1(int u) {
    vis[u] = 1; dis[u][0] = dis[u][1] = 0;
    for (int e = head[u]; e != -1; e = edges[e].next) {
        int v = edges[e].to, w = edges[e].w;
        if (vis[v]) continue;
        dfs1(v);
        int dist = dis[v][0] + w;
        if (dist >= dis[u][0]) {
            dis[u][1] = dis[u][0]; dis[u][0] = dist;
            maxson[u] = v;
        }
        else if (dist > dis[u][1]) 
            dis[u][1] = dist;
    }
}
void dfs2(int u) {
    vis[u] = 1;
    for (int e = head[u]; e != -1; e = edges[e].next) {
        int v = edges[e].to, w = edges[e].w;
        if (vis[v]) continue;
        if (v != maxson[u]) dis[v][2] = max(dis[u][0], dis[u][2]) + w;
        else  dis[v][2] = max(dis[u][1], dis[u][2]) + w;
        dfs2(v);
    }
}

void init() {
    memset(head, -1, sizeof(head));
    memset(vis, 0, sizeof(vis));
    memset(dis, 0, sizeof(dis));
    memset(maxson, 0, sizeof(maxson));
    tot = 0;
}

int main() {
    int u, v, w;
    while (~scanf("%d", &n)) {
        init();
        for (int i = 2; i <= n; i++) {
            scanf("%d%d", &v, &w);
            add(i, v, w);
            add(v, i, w);
        }
        dfs1(1);
        memset(vis, 0, sizeof(vis));
        dfs2(1);
        for (int i = 1; i <= n; i++)
            printf("%d\n", max(dis[i][0], dis[i][2]));
    }
    return 0;
}
  • 38
    点赞
  • 77
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Researcher-Du

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值