Redundant Connetion II
这一周还是图的问题
LeetCode上的685题:https://leetcode.com/problems/redundant-connection-ii/
题目
给出一个有向图和一条额外的边,该图是一个以1,2,…N为节点的根树,额外的边破坏了根树的性质。即给出N条边,找出一条额外边,使得剩下的边能组成根树,如果这样的边有复数条,返回输入中最后的边
Example1:
Input: [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation: The given directed graph will be like this:
1
/ \
v v
2-->3
Example 2 :
Input: [[1,2], [2,3], [3,4], [4,1], [1,5]]
Output: [4,1]
Explanation: The given directed graph will be like this:
5 <- 1 -> 2
^ |
| v
4 <- 3
题解
我们知道如果有向图是一颗树时,使用dfs算法构造dfs生成树将不会遍历到已经遍历的节点。而当树中插入一条额外边时,使用dfs算法会产生下面的情况:
1.找到一条横跨边。如Example1
2.找到一条回边,出现环,图中有入度为0的"根"节点。如Example2
3.找到一条回边,出现环,图中没有入度为0的"根"节点。如下:
```
5 -> 1 -> 2
^ |
| v
4 <- 3
```
2,3的区别可以看成是2中回边没有指向根节点,3的情况则是回边指向了根节点。
在1,2中,必有两个节点指向同一个节点,为了恢复树结构,必须删除其中一条边。第1种情况下删除两条边都可以,选择输入中出现较晚的边即可。第2种情况下下必有一条边在环内,删除该边即可。
第3种情况下没有作为根的节点,删除环中任意一条边都可以。算法遍历(深度优先和宽度优先都可以)有向图找到环,再遍历环中所有边找到输入最晚出现的边。
代码
//节点的数据结构
struct Vertex {
vector<int> child; //子节点
int parent; //父节点
int parent_num; //父节点到该节点的边的编号
bool isvisit; //是否遍历
};
class Solution {
public:
vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
int _size = edges.size();
queue<int> _queue;
vector<Vertex> Vertexs(_size);
vector<int> edge1, edge2;
//遍历给出的所有边,更新每个节点的子节点,父节点等信息
for (int i = 0; i < _size; i++) {
auto &edge = edges[i];
Vertexs[edge[0] - 1].child.push_back(edge[1]);
if (Vertexs[edge[1] - 1].parent == 0) {
Vertexs[edge[1] - 1].parent = edge[0];
Vertexs[edge[1] - 1].parent_num = i;
}
else { //当两个节点指向同一个节点,不记录较后出现的边,并把两条边按顺序记录在edge1,edge2
edge1 = {Vertexs[edge[1] - 1].parent, edge[1]};
edge2 = edge;
}
}
for (auto &v : Vertexs) {
//print(v.child);
v.isvisit = false;
}
//在第1,2种情况下,必定返回edge1,edge2其中一个
//第一种情况下,没有环,返回edge2
//第二种情况下,必有一条边在环中,返回该边
if (!edge1.empty()) {
if(edge1[0] == findRoot(Vertexs, edge1[0]))
return edge1;
else
return edge2;
}
//第三种情况
_queue.push(1);
Vertexs[0].isvisit = true;
vector<int> max_edge;
int n = 0, max_num;
//宽度优先遍历
while(!_queue.empty() || n != _size) {
if (_queue.empty()) {
for (int i = 0; i < _size; i++) {
if (!Vertexs[i].isvisit){
_queue.push(i + 1);
break;
}
}
}
int v = _queue.front();
//cout << "now" << v << endl;
for (auto c : Vertexs[v - 1].child) {
if (Vertexs[c - 1].isvisit) { //若找到回边,记录并立刻跳出遍历
max_num = Vertexs[c - 1].parent_num;
max_edge = {v, c};
break;
}
else {
_queue.push(c);
Vertexs[c - 1].isvisit = true;
}
}
if (!max_edge.empty()) {
break;
}
_queue.pop();
n++;
}
//由找出的回边通过父节点信息遍历整个环,找到出现最晚的环
int cur_v = max_edge[1], next_v = max_edge[0];
while(next_v != cur_v) {
if (Vertexs[next_v - 1].parent_num > max_num) {
max_num = Vertexs[next_v - 1].parent_num;
max_edge = {Vertexs[next_v - 1].parent, next_v};
}
next_v = Vertexs[next_v - 1].parent;
}
return max_edge;
}
//如果一个节点在环中则返回自己,否则一直回溯到根节点的父节点,这里设置为0
int findRoot(vector<Vertex>& Vertexs, int start) {
int p = Vertexs[start - 1].parent;
while (p != 0 && p != start) {
p = Vertexs[p - 1].parent;
}
return p;
}
};