树的直径

定义:树中最远的两个节点之间的距离被称为树的直径。(同时,连接这两点的路径被称为树的最长链)
树的直径的求法
1.树形DP
声明:用d[x]表示从节点x出发走向以x为根的子树,能够到达的最远节点的距离。
edge(x,yi)表示x与子节点yi的连边的长度。

那么,d[x]=max{d[yi]+edge(x,yi)} ,其中,yi为x的子节点。简单来讲,就是从节点x出发走向以x为根的子树,能够到达的最远节点的距离等于从它的子节点出发走向以它的子节点为根的子树,能够到达的最远距离与x与该子节点连边的长度的和的最大值。(有点绕口,emmm,慢慢读)

但是,仅仅求出这个数组是求不出树的直径的。

接下来,再声明一个数组f[]:f[x]表示经过节点x的最长链的长度。

显而易见,我们求完所有节点的f[]之后取最大的那一个就是树的直径了。

那么,怎么求呢?

设yi,yj为x的任意两个子节点。那么f[x]就是由四个部分构成:d[yi],edge(x,yi),d[yj],edge(x,yj)。
假设j

首先想到的是,我们可以用两层for循环来枚举i,j,然后计算出经过每个节点的最长链的长度。
这样的话,时间复杂度比较大,一般的题目不可能只让你求树的直径吧,你再进行点其他操作,差不多就超时了。那么,有没有可以减少时间复杂度的方法呢,emmm,当然是有的。

假设我们用双重for循环,当我们的循环将要枚举到i时,那么i前面节点的d[j]+edge(x,j)已经被计算完了,并且i的根节点x的d[x]里保存了i前面的所有节点的d[j]+edge(x,j)的最大值,那么,我们可以直接用d[x]+d[i]+edge(x,i)更新f[x],再用d[i]+edge(x,i)更新d[x]即可。这样操作以后,我们省去了很多重复的计算,大概可以在O(n)的时间里求出树的直径。

代码:

void dp(int x)//默认以1号节点为根节点开始dp 
{
	vis[x]=1; 
	for(int i=head[x];i;i=net[i])
	{
		if(ver[i]) continue;
		dp(ver[i]);
		ans=max(ans,d[x]+d[ver[i]]+edge[i]);
		d[x]=max(d[x],d[ver[i]]+edge[i]);
	}
} 

注意:由于树中的边存的时候都是按双向边存的,所以要加上一个标记来防止它上下无限遍历。
2.两次BFS或DFS
结论:从任意一个节点出发,通过bfs或dfs对树进行一次遍历,求出与出发点距离最远的节点,记为u;从节点u出发,通过bfs或dfs对树再进行一次遍历,求出与u距离最远的节点,记为v。那么,u到v的路径就是树的一条直径。
讨论证明
来看第一种情况:设p点为树的直径(任意一条)上的一点,那么与p点距离最远的节点肯定是该直径的一个端点,再通过这一端点找到的肯定是另一个端点,这样这两个端点连接的就是一条直径。
第二种情况:设p点不在任何一条树的直径上,但是p到与p点距离最远的节点与树的直径存在交点,那么与p距离最远的节点肯定是树的直径的一个端点,剩下的同第一种情况。为什么呢,假设交点为q,那么p走到q之后,剩下的路径要想最长肯定会沿着直径走。
第三种情况(最后一种):设p点不在任何一条树的直径上,并且p到与p距离最远的节点的路径与树的直径没有交点,这种情况比较难想,画图来看比较直观。
树的直径_第1张图片
上图中,u-v是树的一条直径,q是与p距离最远的点。
假设t节点是p到v的路径与u到v的路径的交点。
我们假设d(x,y)为x到y的距离,那么上图中可以得出d(p,q)>d(p,t)+d(t,v)
根据我们的数学知识,d(p,q)+d(p,t)+d(t,u)>d(p,t)+d(t,v)+d(p,t)+d(t,u) 即d(u,q)>d(u,v)+2*d(p,t)。写到这,我们就能很直观的看出,d(u,q)>d(u,v),但是d(u,v)又是这棵树的直径,因此矛盾。
证明完毕。

下面,我们可以直接用了。

另外,在第二次遍历的过程中,如果我们把访问的节点都储存下来,就能得到这棵树的具体直径。

DFS代码:

#include
#include
#include
#define maxn 1005
using namespace std;
int n;
int ver[maxn*10],head[maxn*10],net[maxn*10];//邻接表存图 
bool vis[maxn];//标记 
int tot;
int sum;
int ans1,ans2;//ans1表示路径长,ans2表示该路径下的叶节点的编号 
void add(int x,int y)
{
	ver[++tot]=y;
	net[tot]=head[x];
	head[x]=tot;
}
void dfs (int node,int lenth)//深搜 
{
	vis[node]=true;
	int p=0;//判断是否搜到子节点了 
	for(int i=head[node];i;i=net[i])
	{
		if(!vis[ver[i]]) p=1,dfs(ver[i],lenth+1),vis[ver[i]]=false;
	}
	if(!p) //搜到子节点了,就比较这条路径的长度和答案 ,然后更新,返回 
	{
		if(ans1<lenth) 
		{
			ans1=lenth;
			ans2=node;
		}
	}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs(1,0);
	vis[1]=false;//把1号节点的标记清除 
	ans1=0;//把ans1清零 
	dfs(ans2,0); 
	printf("%d",ans1);
	return 0;
}

最后,一个常用结论:到一个节点距离最远的点一定是树的直径的一个端点,那么显然树的直径的两个节点到任意一个点的距离中较大的一个一定是这个节点所能到达的最远的距离。

你可能感兴趣的:(图论,树的直径)