题目描述
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一颗包含n个节点的树,标记为0到n-1。给定数字n和n-1条边。现要求:选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
示例:
输入:
4 3 4个顶点 3条边
1 0
1 2
1 3
输出:1
输入:
6 5
0 3
1 3
2 3
4 3
4 5
输出:[3,4] 根为3或4时,均为最小高度树。
题目分析
1、如何存放树;
2、遍历每个节点,并使其为根,以及求出此时树的高度h;
3、
算法思路
求最小高度树,一个直接的想法自然是尝试把每个点作为根节点,从而求出最小高度树。求树的高度可用深度(广度)优先搜索解决。这个算法需要 n 次深度(广度)优先搜索求高度,每次深度(广度)优先搜索的复杂度为 O(n),总的时间复杂度为 O(n^2), 在10^4量级的数据下会超时,因此需要优化。
观察上面的算法,可以发现,在一轮深度优先搜索中,其实不仅得到了以当前节点为根的树的高度,还得到了以每个节点为根的子树的高度,这些高度信息没有用到,被浪费掉了,换根动态规划的思路就是利用这些信息来快速的计算出以其他节点为根时的树高。
更具体的解释见:最小高度树
代码实现
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100;
vector<int> G[maxn];
int n, m; // n: 顶点数 m: 边数
vector<int> height(maxn, 0); // 记录以某一点为根时的树高
vector<int> H0(maxn, 0); // 记录子树高
// DFS: 计算以 0 为根, 各个节点的子树高度
void DFS(int u){ // 求的是以节点 u 为根的子树高度
H0[u] = 1; // 初始只有一个节点 u, 故H0[u] = 1;
int h = 0;
for(int v : G[u]){
if(H0[v] != 0)
continue;
DFS(v);
h = max(h, H0[v]);
}
H0[u] = h + 1;
}
// dfs: 进行换根动态规划, 计算出所有的树高
void dfs(int u){
// 计算子树高的最大值和次大值 此时根节点为 u
int first = 0, second = 0;
for(int v : G[u]){
if(H0[v] > first){
second = first;
first = H0[v];
}
else if(H0[v] > second)
second = H0[v];
}
height[u] = first + 1;
for(int v : G[u]){
// 树高已计算, 则跳过此节点
if(height[v] != 0)
continue;
// 更新以当前节点为根的子树高, 换根到 v
H0[u] = (H0[v] != first ? first : second) + 1;
// 递归进行换根动态规划
dfs(v);
}
}
int main()
{
cin >> n >> m;
int x, y;
while(m--){
cin >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
DFS(0);
dfs(0);
vector<int> ans;
int h = n;
for(int i = 0; i < n; i++){
if(height[i] < h){
h = height[i];
ans.clear();
}
if(height[i] == h)
ans.push_back(i);
}
// 输出结果
for(int i = 0; i < ans.size(); i++)
cout << ans[i] << ' ';
cout << '\n';
return 0;
}