题目:最小高度树 middle
对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,
在所有可能的树中,具有最小高度的树被称为最小高度树。
给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。
格式:
该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,
因此不会同时出现在 edges 里。
package leetCode.BFS;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
public class lc_bfs_310_findMinHeightTrees {
/*
题目:最小高度树 middle
对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,
在所有可能的树中,具有最小高度的树被称为最小高度树。
给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。
格式:
该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,
因此不会同时出现在 edges 里。
示例 1:
输入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]
0
|
1
/ \
2 3
输出: [1]
示例 2:
输入: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2
\ | /
3
|
4
|
5
输出: [3, 4]
说明:
根据树的定义,树是一个无向图,其中任何两个顶点只通过一条路径连接。换句话说,
一个任何没有简单环路的连通图都是一棵树。树的高度是指根节点和叶子节点之间最长向下路径上边的数量。
*/
/*
思路:
首先,我们看了样例,发现这个树并不是二叉树,是多叉树。
然后,我们可能想到的解法是:
1.根据题目的意思,就挨个节点遍历bfs,统计下每个节点的高度,然后用map存储起来,
后面查询这个高度的集合里最小的就可以了。但是这样会超时的。即方法2
2.于是我们看图(题目介绍里面的图)分析一下,发现,越是靠里面的节点越有可能是最小高度树。
这里是分析:
BFS正确性简单证明:
看到题解基本都用的BFS这个方法,因此该方法的正确性似乎并不一目了然,这里简单讲述一下。
证明思路:
对当前的图(初始的图或者删掉了前几层叶子节点的图),我们要找的满足题设的根节点所在位置只有两种可能,
1)一种在当前图的叶子节点(即度为1的节点)
2)一种为内部节点。
a.若选择某叶子节点1为根节点,则该叶子节点1与任意其他叶子节点2的距离必定为: 叶子1-内部节点x-叶子2,
深度为这三段边之和,必大于等于max(内部x-叶子1,内部x-叶子2),
b.所以相比于叶子节点,解空间只能出现在内部节点,每层情况都是这样,
c.所以我们要层层剥开叶子节点直到再无可分的内部节点为止。
3.我们从边缘开始,先找到所有出度为1的节点(即叶子结点),然后把所有出度为1的节点进队列,然后不断地bfs,
每次遇到度为1的结点就入队,即叶子结点入队。最后找到的就是两边同时向中间靠近的节点,
*/
/*
剥洋葱法bfs
*/
public List<Integer> findMinHeightTrees(int n, int[][] edges) {
LinkedList<Integer> ans = new LinkedList<>();
if (n == 1) {
//如果只有一个节点,那么他就是最小高度树
ans.add(0);
return ans;
}
// 建立各个节点的出度表
int[] degree = new int[n];
// 建立图关系,在每个节点的list中存储相连节点
List<List<Integer>> map = new LinkedList<>();
for (int i = 0; i < n; i++) {
map.add(new LinkedList<>());
}
for (int i = 0; i < edges.length; i++) {
degree[edges[i][0]]++;//出度++,即相邻结点数+1
degree[edges[i][1]]++;
map.get(edges[i][0]).add(edges[i][1]);//记录每个相邻节点
map.get(edges[i][1]).add(edges[i][0]);
}
ArrayDeque<Integer> queue = new ArrayDeque<>();
// 把所有出度为1的节点,也就是叶子节点入队
for (int i = 0; i < degree.length; i++)
if (degree[i] == 1)
queue.offer(i);
// bfs开始,循环条件当然是经典的不空判断
while (!queue.isEmpty()) {
// 这个地方注意,ans用于保存每层的叶子结点,表示需要剪去的叶子。每层循环都要new一个新的结果集合,
// 覆盖旧的叶子层结点集合,这样最后保存的就是最里层的叶子结点,也就是最终的最小高度树
ans = new LinkedList<>();
int count = queue.size();//这是每一层的节点的数量
for (int i = 0; i < count; i++) {
int cur = queue.poll();
ans.add(cur);//把当前叶子节点加入结果集
// 当前节点的相邻接点都拿出来,把它们的出度都减1,因为cur节点已经不存在了,
// cur的相邻节点们就有可能变成叶子节点
List<Integer> neighbors = map.get(cur);
for (int neigh : neighbors) {
degree[neigh]--;
// 如果是叶子节点就入队
if (degree[neigh] == 1) {
queue.offer(neigh);
}
}
}
}
return ans;//返回最后一次保存的叶子结点,也就是最里面的结点,也就是根节点
}
/**
* 最正常的bfs想法,超时版本
*
* @return
*/
public List<Integer> findMinHeightTrees2(int n, int[][] edges) {
LinkedList<Integer> res = new LinkedList<>();
if (edges == null || edges.length == 0) {
res.add(0);
return res;
}
int[][] matrix = new int[n][n];
//转化为邻接矩阵关系
for (int i = 0; i < edges.length; i++) {
matrix[edges[i][0]][edges[i][1]] = 1;
matrix[edges[i][1]][edges[i][0]] = 1;
}
int[] isVis = new int[n];
int minHeight = Integer.MAX_VALUE;
int[] heightOfSource = new int[n];
// 得到每个点为源点时的树的高度,存储在heightOfSource中,并记录最小的高度
for (int i = 0; i < n; i++) {
heightOfSource[i] = bfs(i, matrix, isVis);
minHeight = Math.min(minHeight, heightOfSource[i]);
isVis = new int[n];
}
// 拿到最小的高度的源点
for (int i = 0; i < heightOfSource.length; i++) {
if (heightOfSource[i] == minHeight)
res.add(i);
}
return res;
}
/*
单个结点的bfs,n个数多时会超时。
return 返回的是树的高度
*/
public int bfs(int i, int[][] matrix, int[] isVis) {
ArrayDeque<Integer> queue = new ArrayDeque<>();
queue.offer(i);
isVis[i] = 1;
int point = -1;
while (!queue.isEmpty()) {
point = queue.poll();
for (int k = 0; k < matrix[point].length; k++) {
if (matrix[point][k] != 0 && isVis[k] == 0) {
queue.offer(k);
isVis[k] = isVis[point] + 1;
}
}
}
return isVis[point] - 1;
}
}