引言
在面对一个无根树的时候,我们往往无从下手,便随便挑选一个节点作为根节点进行操作。
这是很不负责的一种行为,因为它可能导致很高的复杂度。比如对于一条链,你选择了两头的结点。。你很有可能面对爆栈的风险(NOIP2016D1T2 = =亲身经历)
那么,我们如果把一个很好看的树,比如一个满二叉树,把这个图看成无根图。
你会发现满二叉树的原本的根节点就是最好的选择:它恰巧在整个图的“中央”。
我们把这种最好的根节点的选择称为树的重心或者树的质点。
树的重心:
那么我们如何去求一棵无根树的重心呢?
当然是从重心的性质入手啦!
我们可以认识到,树的重心其实就是在整个图的“中央”,那么这个中央如何进行定义呢?为了保证深度最小(如果你把根节点放在树的重心上,那么它深度当然是最小的啦!)我们可以把深度最小近似看成是子树结点最少。(二者的关系的密切程度随着子树的划分逐渐密切)
即:去掉重心后,使得剩余子树的最多结点数最小。
如何实现?
我们需要保存一个子树最大结点数最小的情况,可以压缩为一个值min
min代表我们找到的最小值,对应一个变量cog为树的重心的编号
现在就是遍历所有的情况了,那么有人心态崩了:我岂不是要对所有的结点来一遍dfs??
但其实不是这样的,我们只需要一次dfs,如果按照楼上的说法,我们解决了太多的重复子问题了。
我们任选一个结点作为本次寻找树的重心的根节点,然后进行一个dfs,我们意识到dfs可以遍历到所有的结点,也就是可以求到所有的划分子树的值。
怎么获取呢?
我们假设当前节点是now,父亲结点是from,now的儿子们是v[]
那么我们可以递归求得v[i]中包含的结点数,而now本身的返回值是sum=v[1]+v[2]+…+v[n]+1
此时我们遗漏了一个子树:那就是以from为根节点的子树!
而这个子树的节点数有点好求:N-sum
N为总结点数,sum为所有儿子的节点数+1
于是我们对比所有的值,取出最大值max
将max与min比较,更新cog
一遍遍历以后我们便取到了树的重心。
代码!(这次我真的不想写注释了)
#include<cstdio>
#define maxn 1005
using namespace std;
struct node{
int to;
int next;
}edge[maxn];
int head[maxn],sub_node[maxn],e,n,m,min=maxn,cog=0;
void add(int u,int v){
edge[++e].next=head[u];
edge[e].to=v;
head[u]=e;
return ;
}
int dfs(int now,int from){
int sum=0,max=0;
for(int i=head[now];i!=0;i=edge[i].next){
int v=edge[i].to;
if(v==from ) continue;
sum+=dfs(v,now);
if(sub_node[v]>max) max=sub_node[v];
}
sum++;
if(max<(n-sum))
max=n-sum;
if(max<min){
min=max;
cog=now;
}
sub_node[now]=sum;
return sum;
}
int main(){
int u,v;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d",&u,&v);
add(u,v);
add(v,u);
}
int s=dfs(1,0);
printf("this tree 's center of gravity is :%d\n",cog);
for(int i=1;i<=n;i++) printf("%d ",sub_node[i]);
return 0;
}