第五周 Redundant Connetion II

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;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值