题目描述:
树是一个无向图,其中任何两个顶点只通过一条路径连接。 换句话说,一个任何没有简单环路的连通图都是一棵树。
给你一棵包含 n 个节点的树,标记为 0 到 n - 1 。给定数字 n 和一个有 n - 1 条无向边的 edges 列表(每一个边都是一对标签),其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条无向边。
可选择树中任何一个节点作为根。当选择节点 x 作为根节点时,设结果树的高度为 h 。在所有可能的树中,具有最小高度的树(即,min(h))被称为 最小高度树 。
请你找到所有的 最小高度树 并按 任意顺序 返回它们的根节点标签列表。
树的 高度 是指根节点和叶子节点之间最长向下路径上边的数量。
解题思路一:拓扑排序(无向图)
结论:逐层删除叶子节点和边,剩下的最后小于等于2个节点即是根节点
1.首先将所有叶子节点压入队列,此时令节点剩余计数 remainNodes=n;
2.同时将当前 remainNodes 计数减去叶子节点数目,将当前队列中叶子节点取出,并将与之相邻的节点的度减少1,重复上述步骤将当前节点中度为 1 的节点压入队列中;
3.重复上述步骤,直到剩余的节点数组 remainNodes≤2 时,此时剩余的节点即为当前高度最小树的根节点。
emplace=push但是二者有一些区别。emplace要新和高效一些。
class Solution {
public:
//拓扑排序实现要点:1.记录当前节点的相邻节点。2.每个节点的度。3.队列实现(度为1入队)
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if(n==1){//如果无向图只有一个节点可直接返回{0};
return {0};//返回值类型是vector<int>
}
vector<int> degree(n);
vector<vector<int>> adj(n);//n个节点
for(auto &edge: edges){//要拓扑排序必须找到每个节点相邻的节点和每个节点的度
adj[edge[0]].emplace_back(edge[1]);
adj[edge[1]].emplace_back(edge[0]);
++degree[edge[0]];
++degree[edge[1]];
}
queue<int> qu;//队列实现拓扑排序
vector<int> ans;//记录最终根节点
for(int i=0;i<n;++i){//队列初始化
if(degree[i]==1){//叶子节点入队
qu.emplace(i);
}
}
int remainNodes=n;
while(remainNodes>2){
int sz=qu.size();
remainNodes-=sz;//减去叶子节点数
for(int i=0;i<sz;++i){
int curr=qu.front();//取队首节点
qu.pop();
for(auto &v: adj[curr]){//取当前节点的相邻节点
if(--degree[v]==1){//节点v度先减1若减去之后v的度为1则入队.
qu.emplace(v);
}
}
}
}
while(!qu.empty()){
ans.emplace_back(qu.front());
qu.pop();
}
return ans;
}
};
emplace操作是C++11新特性,新引入的的三个成员emplace_front、emplace 和 emplace_back。这些操作构造而不是拷贝元素到容器中,这些操作分别对应push_front、insert 和push_back,允许我们将元素放在容器头部、一个指定的位置和容器尾部。
两者的区别 :
当调用insert时,是将对象传递给insert,对象被拷贝到容器中,而当我们使用emplace时,是将参数传递给构造函,emplace使用这些参数在容器管理的内存空间中直接构造元素。
复杂度分析
时间复杂度:O(n),其中 n 是为节点的个数。图中边的个数为 n−1,因此建立图的关系需要的时间复杂度为 O(n),通过广度优先搜索需要的时间复杂度为 O(n+n−1),求最长路径的时间复杂度为 O(n),因此总的时间复杂度为 O(n)。
空间复杂度:O(n),其中 n 是节点的个数。由于题目给定的图中任何两个顶点都只有一条路径连接,因此图中边的数目刚好等于 n−1,用邻接表构造图所需的空间刚好为 O(2×n),存储每个节点的距离和父节点均为 O(n),使用广度优先搜索时,队列中最多有 n 个元素,所需的空间也为 O(n),因此空间复杂度为 O(n)。
解题思路二:广度优先搜索BFS(看注释)
结论:找最长的一条路径
1.从节点0开始找最远节点x
2.从节点x开始找最远节点y
3.x-y即是最远路径
class Solution {
public:
//BFS算法要点:1.记录当前节点的相邻节点。2.访问数组(每个节点仅访问一次)3.队列实现(未访问入队)
int findLongestNode(int u, vector<int> & parent, vector<vector<int>>& adj) {
//parent记录从u节点开始每个节点的父节点,BFS算法
int n = adj.size();
queue<int> qu;
vector<bool> visit(n);//访问数组。
qu.emplace(u);
visit[u] = true;
int node = -1;
while (!qu.empty()) {
int curr = qu.front();
qu.pop();
node = curr;//记录队列中广度搜索最后一个节点,即是距离节点u最远的节点。
for (auto & v : adj[curr]) {//每次广度搜索一层
if (!visit[v]) {
visit[v] = true;
parent[v] = curr;
qu.emplace(v);
}
}
}
return node;
}
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if (n == 1) {
return {0};
}
vector<vector<int>> adj(n);
for (auto & edge : edges) {
adj[edge[0]].emplace_back(edge[1]);
adj[edge[1]].emplace_back(edge[0]);
}
vector<int> parent(n, -1);
/* 找到与节点 0 最远的节点 x */
int x = findLongestNode(0, parent, adj);
/* 找到与节点 x 最远的节点 y */
int y = findLongestNode(x, parent, adj);
/* 求出节点 x 到节点 y 的路径 */
vector<int> path;//记录一条从x到y的路径
parent[x] = -1;//parent此时记录的是x开始广度搜索之后每个节点的父节点,可想而知x是父节y是最后一个子节点,即一条从x到y的路径。
while (y != -1) {
path.emplace_back(y);
y = parent[y];
}
int m = path.size();
if (m % 2 == 0) {//如果m为偶数,此时最小高度树的根节点为{path[m / 2 - 1], path[m / 2]}
return {path[m / 2 - 1], path[m / 2]};
} else {//如果m为奇数,此时最小高度树的根节点为 {path[m / 2]}
return {path[m / 2]};
}//这是结论。证明可以看链接:https://leetcode-cn.com/problems/minimum-height-trees/solution/zui-xiao-gao-du-shu-by-leetcode-solution-6v6f/
}
};
解题思路三:深度优先搜索DFS(递归传送的参数可以稍微注意一下)
和广度优先搜索差不多
class Solution {
public:
//DFS算法要点:1.记录当前节点的相邻节点。2.递归实现(若dist不为-1则深度搜索一遍。)即记录每个节点到当前节点的深度。
void dfs(int u, vector<int> & dist, vector<int> & parent, const vector<vector<int>> & adj) {
for (auto & v : adj[u]) {
if (dist[v] < 0) {
dist[v] = dist[u] + 1;
parent[v] = u;
dfs(v, dist, parent, adj);
}
}
}
int findLongestNode(int u, vector<int> & parent, const vector<vector<int>> & adj) {
int n = adj.size();
vector<int> dist(n, -1);
dist[u] = 0;
dfs(u, dist, parent, adj);
int maxdist = 0;
int node = -1;
for (int i = 0; i < n; i++) {
if (dist[i] > maxdist) {
maxdist = dist[i];
node = i;
}
}
return node;
}
vector<int> findMinHeightTrees(int n, vector<vector<int>>& edges) {
if (n == 1) {
return {0};
}
vector<vector<int>> adj(n);
for (auto & edge : edges) {
adj[edge[0]].emplace_back(edge[1]);
adj[edge[1]].emplace_back(edge[0]);
}
vector<int> parent(n, -1);
/* 找到距离节点 0 最远的节点 x */
int x = findLongestNode(0, parent, adj);
/* 找到距离节点 x 最远的节点 y */
int y = findLongestNode(x, parent, adj);
/* 找到节点 x 到节点 y 的路径 */
vector<int> path;
parent[x] = -1;
while (y != -1) {
path.emplace_back(y);
y = parent[y];
}
int m = path.size();
if (m % 2 == 0) {
return {path[m / 2 - 1], path[m / 2]};
} else {
return {path[m / 2]};
}
}
};