Contents
  1. 1. 引言
  2. 2. 树的重心:
  3. 3. 如何实现?
  4. 4. 代码!(这次我真的不想写注释了)

引言

在面对一个无根树的时候,我们往往无从下手,便随便挑选一个节点作为根节点进行操作。
这是很不负责的一种行为,因为它可能导致很高的复杂度。比如对于一条链,你选择了两头的结点。。你很有可能面对爆栈的风险(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

一遍遍历以后我们便取到了树的重心。


代码!(这次我真的不想写注释了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

#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;
}

Contents
  1. 1. 引言
  2. 2. 树的重心:
  3. 3. 如何实现?
  4. 4. 代码!(这次我真的不想写注释了)