树的重心
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围
1≤n≤10510^5105
输入样例
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出样例:
4
思路分析
在解题之前先理解一下问题问的是什么意思,所谓“看得清比走得快更重要,因为走得对才能走得远”,如果理解错题目表达的意思,最后只会功亏一篑
这个题的意思是,当去掉某个结点后,使得剩下的连通块的最大值最小
求这个最小的最大值

如果删去结点1的话,剩下三个连通块,最大是4

如果删去结点2的话,剩下三个连通块,最大是6
…
如果删去结点9的话,剩下一个连通块,最大是8
因此最大值最小是4
因此我们有了思路,遍历每个结点然后求每个结点去除后最大连通块的值
因为每个结点遍历一次,树是一种特殊的图,因此我们可以用图的深度优先遍历
在遍历之前,我们先搞清楚图是怎么存的
图的表示方式有两种:邻接矩阵(二维数组);邻接表(链表)
- 邻接矩阵
邻接矩阵是表示图形中顶点之间相邻关系的矩阵,对于 n 个顶点的图而言,矩阵的 row 和 col 表示的是 1…n 个点。

- 邻接表
邻接矩阵需要为每个顶点都分配 n 个边的空间,其实有很多边都是不存在,会造成空间的一定损失。
邻接表的实现只关心存在的边,不关心不存在的边。因此没有空间浪费,邻接表由数组 + 链表组成
若无向图中有n个顶点、e条边,则其邻接表需要n个头结点和2e个表结点
若有向图中有n个顶点、e条边,则其邻接表需要n个头结点和e个表结点
- 顶点ViV_iVi的出度为ViV_iVi所在单链表中结点个数
- 顶点ViV_iVi的入度需要遍历所有单链表计算指向该结点的个数

而无向图又是一种特殊的有向图,只需要将边存储两次即可
public static void add(int from, int to) {
value[index] = to;
next[index] = head[from];
head[f rom] = index++;
}
这种方式是用数组模拟链表实现的邻接表的添加操作
其中value数组存储的是结点的值,next数组存储的下一个结点的索引,head数组存储的是第一个结点的索引,index是一个自增索引

上图是对以上代码的插入过程的一个实例(其实就是静态链表)
接着我们来看一下最关键的深度优先遍历的代码实现
/**
* 该方法返回以该结点为根结点的点的数量
*/
public static int dfs(int u) {
used[u] = true;
int sum = 1, size = 0;
for (int i = head[u]; i != -1; i = next[i]) {
int v = value[i];
if (!used[v]) {
int s = dfs(v);
sum += s;
size = Math.max(size, s);
}
}
size = Math.max(size, n - sum);
ans = Math.min(ans, size);
return sum;
}
其中size表示去掉当前点后剩下连通块中节点的最大数量,也就是我们需要的那个变量,题目最终要求的ans是所有点的size的最小值。s表示当前点的孩子所返回的这个孩子子孙节点的数量。sum表示当前点所有子孙的数量。
所以某个子节点的s其实是这个儿子自己的sum+1(加上儿子)得到的。所以sum是所有儿子的s之和。所以size是把所有儿子的s取最大之后,再与不是子孙的连通块(节点上方的)做比较(即n-sum-1),取最大得到的(对于根节点也成立,根节点上方的节点数为0,所以size最终会取得它剩下儿子中子孙最多的那个数,也就是最大的s)
举个例子,在下图中,如果我们现在删去4结点,那么将会有三个连通块


下面两个小的连通块可以通过调用dfs()方法返回连通块结点的个数,分别是1和2,每次都是和当前size比较取最大值,当跳出循环后,再用总数减去以当前结点为根结点的结点总数就是上面无法通过调用dfs()方法得到的连通块的结点个数,再次进行比较取最大值。
ans是全局变量,保存的是最小最大连通块的值
所有代码如下
import java.io.*;
import java.util.*;
public class Main {
static final int N = 100010;
static int[] head = new int[N];
static int[] value = new int[2 * N];
static int[] next = new int[2 * N];
//size是去除这个某个结点之后,最大的连通快中结点数量
static int ans = N;
static int size, index, n;
static boolean[] used = new boolean[N];
public static void main(String[] args) throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String row1 = br.readLine();
n = Integer.parseInt(row1);
//将head数组初始化为-1
Arrays.fill(head, -1);
for (int i = 0; i < n - 1; i++) {
String row = br.readLine();
String[] items = row.split(" ");
int from = Integer.parseInt(items[0]);
int to = Integer.parseInt(items[1]);
add(from, to);
add(to, from);
}
dfs(1);
System.out.println(ans);
}
public static void add(int from, int to) {
value[index] = to;
next[index] = head[from];
head[from] = index++;
}
/**
* 该方法返回以该结点为根结点的点的数量
*/
public static int dfs(int u) {
used[u] = true;
int sum = 1, size = 0;
for (int i = head[u]; i != -1; i = next[i]) {
int v = value[i];
if (!used[v]) {
int s = dfs(v);
sum += s;
size = Math.max(size, s);
}
}
size = Math.max(size, n - sum);
ans = Math.min(ans, size);
return sum;
}
}
372

被折叠的 条评论
为什么被折叠?



