树的直径两种求法

首先先介绍一下什么是树的直径,树的直径就是树中所有最短路经距离的最大值。

求树的直径通常有两种方法,一种是通过两次搜索(bfs和dfs均可),另一种就是通过树形dp来求了。

先来介绍一下用两次深搜来求树的直径:

先从图上任意一点,搜索整棵树,找到距离该点最远的点,再用距离该点最远的点搜索一次,即可得到树的直径。

现在来给出证明:

图像说明,两个椭圆形代表抽象子树,使得结论具有一般性:

由直径的定义我们可以知道由直径的一个端点搜索出的最大距离一定是直径的长度,那么我们现在就要证明由图中随机的一个点搜索出来的最远点一定是直径的一个端点,下面主要用反证法来证明。

以下图形均假设e1 e2为直径两端点

第一种情况,我们一开始所选取的点在直径上

我们一开始选定的点是e0,假如通过e0搜到的距离最远的点是e3,则说明

d(e0,o)+d(o,e3)>= d(e0,o)+d(o,e2),等价于

d(o,e3)>= d(o,e2)则 d(e1,o)+d(o,e3)>= d(e1,o)+d(o,e2)这与e1,e2是直径的两个端点矛盾,故不成立

第二种情况:我们一开始所选取的点不在直径上,且e0与e3与直径不相交,设直径上的o点与e0,e3路径上的e4点相交

则d(e0,e4)+ d(e4,e3)>= d(e0,e4)+ d(e4,o)+d(o,e2)

等价于 d(e4,e3)>= d(e4,o)+d(o,e2)

则d(e1,o)+  d(o,e4)+ d(e4,e3)>=d(e1,o)+ d(o,e2)

这与e1,e2是直径的两个端点矛盾,故不成立

最后一种情况:我们一开始所选取的点不在直径上,且e0与e3与直径相交,不妨设交点为o

则d(e0,o)+d(o,e3)>= d(e0,o)+d(o,e2),等价于

d(o,e3)>= d(o,e2)则 d(e1,o)+d(o,e3)>= d(e1,o)+d(o,e2)

 这与e1,e2是直径的两个端点矛盾,故不成立

希望大家好好理解这个图,在以后的很多关于树的直径的题目中的结论证明方法都类似于上面的证明方法。

核心代码:

void dfs(int x,int father)
{
	for(int i=h[x];i!=-1;i=ne[i])//用链式前向星存树
	{
		int j=e[i];
		if(j==father) continue;//树是一种有向无环图,只要搜索过程中不返回父亲节点即可保证不会重复遍历同一个点。
		d[j]=d[x]+w[i];//更新每个点到起始点的距离(在树中任意两点的距离是唯一的)
		dfs(j,x);//继续搜索下一个节点
	}
}

ll ans=-1;
	for(int i=1;i<=n;i++)
		if(d[i]>ans)
		{
			ans=d[i];
			e=i;记录与搜索点距离最远的点
		}

下面我来介绍一下树形dp来求树的直径:

不难想到,距离直径上的一个点最远的点和次远的点一定是直径的两个顶点,所以我们只要能找到距离每一个点的最远距离和次远距离不就能找到树的直径了么,但是有一点需要注意,就是距离一个点的最远距离和次远距离不能与重合部分。下面我来对具体实现方面展开说明。

我们需要开两个数组F1[]和F2[]分别记录到某个点的最远距离和次远距离,这样我们最后把每个点的两个数组相加取个最大值就可以找到树的直径了。

核心代码:

void dp(int x,int father)
{
	for(int i=h[x];i!=-1;i=ne[i])链式前向星存树
	{
		int j=e[i];
		if(j==father) continue;//防止原路返回
		dp(j,x);//dp过程应该是由叶节点开始的,也就是说先递归到叶节点再开始进行状态转移
		if(f1[x]<f1[j]+w[i])//如果子节点的最大距离+子节点与父节点之间边的权重大于父节点的最大距离,那么父节点的最大距离和次大距离都要得到相应更新
		{
			f2[x]=f1[x];
			f1[x]=f1[j]+w[i];
		}
		else if(f2[x]<f1[j]+w[i])//若子节点的最大距离+子节点与父节点之间边的权重小于父节点的最大距离,再判断与父节点的次大距离的关系
			f2[x]=f1[j]+w[i];
		ans=max(ans,f1[x]+f2[x]);//在搜索过程中找到树的直径
	}
}

下面我将给出一道例题,并用两种方法给出解答:

After hearing about the epidemic of obesity in the USA, Farmer John wants his cows to get more exercise, so he has committed to create a bovine marathon for his cows to run. The marathon route will include a pair of farms and a path comprised of a sequence of roads between them. Since FJ wants the cows to get as much exercise as possible he wants to find the two farms on his map that are the farthest apart from each other (distance being measured in terms of total length of road on the path between the two farms). Help him determine the distances between this farthest pair of farms.

Input

* Lines 1.....: Same input format as "Navigation Nightmare".

Output

* Line 1: An integer giving the distance between the farthest pair of farms.

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S

Sample Output

52

我大概简化下题意吧:就是先输入两个整数,第一个整数表示点的数量,第二个整数表示边的数量,下面给出每条边的起始点和终止点以及该边的权值和方向(具体方向是用不到的,计算机中的方向只需考虑始点与终点即可),求该树的直径。

代码1(dfs)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=1000013;
typedef long long ll;
ll h[N],ne[N],e[N],idx,d[N],w[N];
bool vis[N];
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dfs(int x,int father)
{
	for(int i=h[x];i!=-1;i=ne[i])//用链式向前星存树
	{
		int j=e[i];
		if(j==father) continue;//树是一种有向无环图,只要搜索过程中不返回父亲节点即可保证不会重复遍历同一个点。
		d[j]=d[x]+w[i];//更新每个点到起始点的距离(在树中任意两点的距离是唯一的)
		dfs(j,x);//继续搜索下一个节点
	}
}
int main()
{
	int n,m,e;
	cin>>n>>m;
	memset(h,-1,sizeof h);
	char c[2];
	int x,y,z;
	while(m--)
	{
		scanf("%d%d%d%s",&x,&y,&z,c);
		add(x,y,z);add(y,x,z);
	}
	dfs(1,0);
	ll ans=-1;
	for(int i=1;i<=n;i++)
		if(d[i]>ans)
		{
			ans=d[i];
			e=i;
		}
	memset(d,0,sizeof d);
	dfs(e,0);
	for(int i=1;i<=n;i++)
		if(d[i]>ans)
			ans=d[i];
	printf("%lld",ans);
	return 0;
}

代码2(树形dp)

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e6+10;
int n,m;
long long ans;
long long e[N],h[N],ne[N],idx,w[N];
long long f1[N],f2[N];
void add(int x,int y,int z)
{
	e[idx]=y;
	w[idx]=z;
	ne[idx]=h[x];
	h[x]=idx++;
}
void dp(int x,int father)
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(j==father) continue;//防止原路返回
		dp(j,x);
		if(f1[x]<f1[j]+w[i])
		{
			f2[x]=f1[x];
			f1[x]=f1[j]+w[i];
		}
		else if(f2[x]<f1[j]+w[i])
			f2[x]=f1[j]+w[i];
		ans=max(ans,f1[x]+f2[x]);
	}
}
int main()
{
	cin>>n>>m;
	char c;
	int x,y,z;
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>z>>c;
		add(x,y,z);
		add(y,x,z);
	}
	dp(1,0);
	printf("%lld",ans);
	return 0;
}

这就是基本的树的直径求解的两种方法。

如果有更好的方法,欢迎大家在评论区里留言!

### 计算木枝干直径的方法 在计算木枝干的直径时,可以采用几何建模和物理模拟相结合的方式。以下是几种可能的技术路径: #### 几何建模方法 如果目标是在虚拟环境中生成一棵,则可以通过定义参数化模型来控制枝干的直径。Unity3D 提供了一个内置的编辑器工具[^2],允许用户从主干开始构建整棵,并调整各部分的比例关系。在这种情况下,枝干的直径可以直接作为输入参数设置。 对于更复杂的场景,还可以引入基于分形理论或者L-System的语言描述法来自动生成自然形态的植物结构。这些方法通常会考虑生长规律以及环境因素的影响,在此过程中也会涉及对不同层次分支粗细比例的设计。 #### 物理仿真方法 当需要更加真实地表现风吹草动下的动态效果时,就需要运用物理学原理来进行数值分析了。文献提到过一种假设条件:“我们简化地假定木的动力学特性”,其中包括一致性的密度分布、忽略掉局部间相互干扰(如无碰撞检测)[^3]等设定前提下推导出来的公式可用于估算实际物体尺寸大小范围内的合理近似值。 具体而言,为了得到某个特定位置处横截面积A(x),可以根据质量守恒定律得出相应长度l上的变化趋势dA/dx=-k*A^n其中n>=1取决于材料属性等因素;而最终得半径r即满足π*r²=A即可完成整个流程操作步骤说明完毕之后再给出一段总结性质的话收尾比较好一点吧比如下面这样写就不错哦~ 另外值得注意的是上述两种途径各有优劣之处需视具体情况灵活选用合适方案解决问题才是王道呢! ```python import math def calculate_diameter(mass, length, constant_k=0.5): """ Calculate the diameter of a tree branch based on mass and length. Parameters: mass (float): Mass per unit length of the branch. length (float): Length of the segment along which to compute the radius change. constant_k (float): Empirical coefficient related to material properties. Returns: float: Diameter at end point after specified distance traveled through growth process. """ initial_area = 4 * math.pi ** 2 / (constant_k**(1/2)) # Simplified assumption about starting condition final_area = ((initial_area)**(-constant_k*length)+mass)*((initial_area)**constant_k)-mass return math.sqrt(final_area/math.pi) example_result = calculate_diameter(1e-3, 5) print(f"The calculated diameter is approximately {round(example_result, 6)} meters.") ```
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值