AcWing 846. 树的重心(树与图的深度优先遍历)

题目链接 : 点击查看

题目描述 :

给定一颗树,树中包含 n 个结点(编号 1∼n )和 n−1 条无向边。

请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。

重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

输入输出格式 :

输入

第一行包含整数 n,表示树的结点数。

接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。

输出

输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。

输入输出样例 :

输入

9
1  2
1  7
1  4
2  8
2  5
4  3
3  9
4  6

输出

4

题目分析 :

本题所要求的是删去树的重心之后,形成的连通块中节点最多的那个连通块的节点数量。树的重心即为在去除某个点之后,形成的连通块中最大节点数目,在删除其余节点中形成的连通块所得最大节点数目最少,那么那个点即为树的重心。那么这个题该怎么入手呢?我们可以这样想,删除树中的某个节点后,可以将分成两个部分,1 . 此节点的父节点所形成的连通块 2.此节点的子节点所形成的连通块 。如图。

父节点所形成的连通块可看成一个大连通块,子节点所形成的连通块可以根据子节点的个数分成不同的连通块。然后我们所要做的就是,从这些不同连通块中寻找最大节点数目是多少。其父节点所形成连通块节点数目可能不好直接求出,但其子节点所形成的连通块节点数目则相对好求一些。我们可以从被删去的节点,比如图中的3,开始对图进行深度优先搜索,其搜索的内容就是被删去节点的子树,同时我们定义一个sum变量,用来储存子树中节点数目,最后用节点总数n减去 sum即为父节点形成连通块的节点数目。比较每个模块的节点数目,找出最大值并记录。然后再找出删除每个节点,连通块节点最大值,最后将每个最大值进行比较,取最小的那个即可。

那么该怎么具体去实现呢?要想遍历图(树是一种无环连通图),首先要对图/树进行存储,在此我们采用邻接表的方法对树的节点进行存储。即将每个节点和与它直接相连的节点连成一个链表,其中数组h[i],表示编号为i的树的节点的邻接表的头结点,插入数据时我们采用头插法,(建立邻接表的代码与hash的链地址法差不多)详细了解请看代码。然后我们要对图进行深度优先遍历,在此之前需要如下准备 : 1. bool st[i] : 标明i节点的状态,防止重复搜索。 2.  节点个数n定义在全局区,方便计算父节点形成连通块的节点数目 。 3.全局区定义ans记录删去重心后最大节点数目。在dfs函数中,要现将当前点的st置为true表明已经搜索过,同时定义size记录每个子节点形成的连通块节点数,定义sum记录子树的节点总数。然后我们按照遍历遍历单链表的方式写一个for循环,对当前节点的整个邻接表进行遍历,每一次循环n[i]储存的即为树中节点编号,如果没当前点没被搜索过(!st[n[i]]),我们继续向下搜索(dfs[n[i]]) 。而且我们的dfs函数返回值为sum(子树的节点总数),用int s = dfs(n[i])来接收,在搜索完回溯过程中sum+=s,不断更新sum的值,size = max(size, s),不断更新size的值,注意两者之间的不同,不断进行+=操作可得所有子树(子节点形成的连通块)的节点和,max操作则得到的是最多子树的节点数目。for循环内我们遍历的是子节点所形成的连通块,for循环外,我们要对父节点所形成连通块进行处理,取size和n -sum中的最大值,作为删去这个节点形成连通块的最大值,然后ans在所有删除节点的情况中取最小值即可,即ans = min(ans, size)。在主函数中,我们直接调用dfs(1),因为1为树的根节点,从1开始可以遍历到所有节点。最后输出ans即可。详见如下代码。

代码 :
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 7, M = N * 2;//无向图最多开2 * N条边 
int n;
int h[N], e[M], ne[M], cur;//h[i]储存的是树中节点为i的邻接表的头结点 
int ans = N;//表示重心的所有的子树中,最大的子树的结点数目
bool st[N];//记录节点的状态 
void add(int a, int b) {
	e[cur] = b, ne[cur] = h[a], h[a] = cur ++ ; //创建每个节点的邻接表 ,此处用的是头插法 
}
int dfs(int u) {
	st[u] = true;//先标明此节点已被用过 
	int size = 0, sum = 1;//size记录以当前节点为重心所有连通块节点数的最大值 ,sum记录以当前点为根节点的子树节点数 ,初始值为1 
	for (int i = h[u]; i != -1; i = ne[i]) {//for循环遍历每个树节点所有与之相连的节点 
		int j = e[i];//检查与i相连的点是否已经被搜索过 
		if (!st[j]) {
		   int s = dfs(j);
	       size = max(size, s);
	       sum += s;
		}	
	}
	size = max(size, n - sum );
	ans = min(ans, size);//重心定义 
	return sum ;
}
int main() {
	cin >> n;
	memset(h, -1, sizeof(h));
	for (int i = 0; i < n - 1; i ++ ) {
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b), add(b, a);//树是一种无向无环连通图,我们可以按建立图的建立方式建立树,因此建立树时建立双向边(因为无向) 
	}
	dfs(1);//树的节点从1开始 
	cout << ans << endl;
	return 0;
}

--------------------------------------------------------------------------------------

下面我们给出图与树的深度优先遍历相关模板

时间复杂度 O(n+m), n 表示点数,m 表示边数
int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

在森林中麋了鹿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值