求树的直径

树的直径,即树上的最长路径,显然,树的直径可以有很多条(考虑一棵菊花)。

接下来我们考虑如何求出一棵树的直径。有很多种O(n)的算法。

算法1:我们任取树中的一个节点x,找出距离它最远的点y,那么点y就是这棵树中一条直径的一个端点。我们再从y出发,找出距离y最远的点就找到了一条直径。这个算法依赖于一个性质:对于树中的任一个点,距离它最远的点一定是树上一条直径的一个端点。

下面给出证明。

考虑这样一棵树,我们假设AB是树的直径,C的最远点为D,那么有AC<CD,a+c<d,因为c>0,所以a<d+c,故有a+b<b+c+d,AB<BD,与假设AB是直径矛盾,故性质得证。

算法2:首先,先将无根树转成有根树,定义F[i]表示从i出发向远离根节点的方向走的最长路径的长度,G[i]表示从i向远离根节点的方向走的次长路径的长度。注意F[i]和G[i]不能沿着i的同一个儿子走。特别地,如果i只有一个儿子,那么G[i]=0。答案为max(F[i]+G[i])。

下面是代码实现。再次感谢Anonymous366提供代码,我也进行了修改,并加了注释。这份代码可以求出带权树中的直径,如果只是一棵普通的树,那么val赋为1即可。

作者:zhanxufeng
来源:CSDN
原文:https://blog.csdn.net/zhanxufeng/article/details/80715185
版权声明:本文为博主原创文章,转载请附上博文链接!

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=20100;
int n,father;
int siz[maxn];//siz保存每个节点的子树大小。
bool vist[maxn];
int CenterOfGravity=0x3f3f3f3f,minsum=-1;//minsum表示切掉重心后最大连通块的大小。
vector<int>G[maxn];
void DFS(int u,int x){//遍历到节点x,x的父亲是u。
	siz[x]=1;
	bool flag=true;
	for(int i=0;i<G[x].size();i++){
		int v=G[x][i];
		if(!vist[v]){
			vist[v]=true;
			DFS(x,v);//访问子节点。
			siz[x]+=siz[v];//回溯计算本节点的siz
			if(siz[v]>n/2) flag=false;//判断节点x是不是重心。
		}
	}
	if(n-siz[x]>n/2) flag=false;//判断节点x是不是重心。
	if(flag && x<CenterOfGravity) CenterOfGravity=x,father=u;//这里写x<CenterOfGravity是因为本题中要求节点编号最小的重心。
}
void init(){
	memset(vist,false,sizeof(vist));
	memset(siz,0,sizeof(siz));
	minsum=-1;
	CenterOfGravity=0x3f3f3f3f;
	for(int i=0;i<maxn;i++) G[i].clear();
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		init();
		for(int i=1;i<n;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			G[u].push_back(v);
			G[v].push_back(u);
		}
		vist[1]=1;
		DFS(-1,1);//任意选取节点作为根,根节点的父亲是-1。
		for(int i=0;i<G[CenterOfGravity].size();i++)
			if(G[CenterOfGravity][i]==father) minsum=max(minsum,n-siz[CenterOfGravity]);
			else minsum=max(minsum,siz[G[CenterOfGravity][i]]);
		printf("%d %d\n",CenterOfGravity,minsum);
	}
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值