注意事项:
代码涉及到手写单链表,可以看我之前的文章:java单链表_数组模拟
题目:
给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。
请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。
重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
输入格式
第一行包含整数 n,表示树的结点数。
接下来 n−1 行,每行包含两个整数 a 和 b,表示点 a 和点 b 之间存在一条边。
输出格式
输出一个整数 m,表示将重心删除后,剩余各个连通块中点数的最大值。
数据范围
1≤n≤1e5
输入:
9
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
输出:
4
public class 树与图的DFS深度优先遍历_树的重心 {
//初始化, h数组存储所有的链表head,e存储链表元素,ne存储链表的下一位指向,st判断当前节点有没有被走过
//e和ne都是开N的二倍,ans存储的是每一个连通块最大值中的最小值
public static int N = 1000010, M = N*2, index = 0, ans = N, n;
public static int[] h = new int[N], e = new int[M], ne = new int[N];
public static boolean[] st = new boolean[N];
public static void main(String[] args) throws IOException {
//初始化,h数组也就是所有的head设置为-1
Arrays.fill(h, -1);
Scanner in = new Scanner(System.in);
n = in.nextInt();
//因为是双向图,所以建立连接的时候,需要两个点互相指向
for (int i = 0; i<n-1; i++){
int x = in.nextInt(), y = in.nextInt();
insert_to_head(x, y);
insert_to_head(y, x);
}
dfs(1);
System.out.println(ans);
}
//dfs返回以x为根节点的子树中点的数量
public static int dfs(int x) {
//首先st将当前点标记为走过了,sum存储的是当前点的子节点数量包括自己
//res存储删除当前点后的每一个连通块的最大值
st[x] = true;
int sum = 1, res = 0;
for (int i = h[x]; i!=-1; i = ne[i]) { //遍历当前节点所连接的所有节点
int j = e[i];
if (!st[j]) { //判断这个点有没有被走过
int s = dfs(j);
res = Math.max(res, s);
sum += s;
}
}
//res更新为res和之前节点数量中较大的
//ans更新为当前res和之前ans中较小的
res = Math.max(res, n - sum);
ans = Math.min(res, ans);
return sum;
}
//在头节点处插入元素,是单链表模拟的知识
public static void insert_to_head(int x, int y) {
e[index] = y;
ne[index] = h[x];
h[x] = index++;
}
}
思路:
就拿输入为例子,图画出来是这样的:
当我们将1这个点去掉的话,剩下3个连通块的节点数量分别为1,4,3,最大的是4,
然后对每一个点都去找到将它去掉后的剩余连通块中节点数量的最大值,再从这些最大值中找到最小值,就是答案
同时还需要注意一点,由于我们只进行一次dfs,那么会产生一个问题:
比如1为根节点,那么(7)(4639)(285)这三个区间是会被计算的,
进入(4639)这个区间,4会被视为根节点,向下还剩两个区间(6)(39),
大家可能已经发现问题了,如果按照这样计算,去除4这个根节点后本该有三个区间(71285)(6)(39),但我们这里向下dfs只能算到下面的两个区间(6)(39),但想得到之前的那个区间的节点数也很简单,
就是用 总数n 减去 从4为根节点的区间的节点个数sum 即可,
然后再在这几个值之间取max就可以啦。
声明:
算法思路来源为y总,详细请见https://www.acwing.com/
本文仅用作学习记录和交流