代码随想录(十二)——图论

并查集

并查集主要有三个功能。

  1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个
  2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上
  3. 判断两个节点是否在同一个集合,函数:isSame(int u, int v),就是判断两个节点是不是同一个根节点

并查集可以解决的问题:两个节点是否在一个集合,也可以将两个节点添加到一个集合中。

难点在于根的路径压缩的理解

寻找图中是否存在路径

1971. 寻找图中是否存在路径

有一个具有 n 个顶点的 双向 图,其中每个顶点标记从 0n - 1(包含 0n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。

请你确定是否存在从顶点 source 开始,到顶点 destination 结束的 有效路径

给你数组 edges 和整数 nsourcedestination,如果从 sourcedestination 存在 有效路径 ,则返回 true,否则返回 false

java 复制代码
class Solution {
public:
    bool validPath(int n, vector<vector<int>>& edges, int source, int destination) {
        /*
            深搜 / 广搜
            这里选择使用并查集进行实现
            使用并查集判断两个元素是否在同一个集合内部:
                step1: 使用join(u,v)把每条边加入到并查集
                step2: 使用 isSame(int u,int v) 判断是否是同一个根【即是否属于同一个集合】
        */
        // step0: 并查集初始化
        init(n);
        // step1: 把每条边加入并查集
        for(vector<int> edge : edges) { // 每个元素就是一条边
            join(edge[0],edge[1]);
        }
        // step2: 使用 isSame(int u,int v) 判断是否是同一个根
        return isSame(source, destination);
    }
private:
    vector<int> father  = vector<int>(200001,0) ; // 按照节点的大小定义数组长度
    void init(int n) { // 并查集初始化
        for(int i = 1; i <= n; i++) {
            father[i] = i; //初始化。每个元素都是自己的根
        }
    }
    // 并查集里寻找根的过程
    int find(int u) {
        return u== father[u] ? u : father[u] = find(father[u]);
    }

    // 判断 u 和 v 是否找到同一个根
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }
    // 把 v-> u 这条边加入并查集 father[v] = u
    void join(int u, int v) {
        // 先判断两个元素是否在同一个集合内部
        u = find(u);
        v = find(v);
        if(u == v) return;
        father[v] = u;
    }
};

冗余连接

684. 冗余连接

树可以看成是一个连通且 无环无向图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edgesedges[i] = [ai, bi] 表示图中在 aibi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的那个。

java 复制代码
class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        /**
            图论:删除相对于数来说的多余的一条边
            使用并查集的思想:
                把每条边都加入到其中,如果在加入的时候发现两个顶点已经同根;(即在一个并查集中)
                此时就说明这条边是一条冗余边,删除这条边即可
        */
        int[] ans = null;
        init(edges.length);
        for(var edge : edges) {
            if(!join(edge[0],edge[1])) {
                ans = edge;
                break;
            }
        }
        return ans;
    }
    private int[] father;
    private void init(int vLen) { // 并查集的初始化 // 传入顶点数
        father = new int[vLen+1];
        for(int i=0; i < vLen; i++) {
            father[i] = i; // father[i] = i; 自身是自身的根,即刚开始所有节点都是单项的
        }
    }
    
    // 找到一个元素的根
    int find(int u) {
        return father[u] == u ? u: (father[u] = find(father[u]));
    }


    // 把 u->v 加入并查集
    private boolean join(int u, int v) {
        u = find(u);
        v = find(v);
        if(u == v) return false;
        father[u] = v;
        return true;
    }

    // 判断两个节点是否同根
    public boolean isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }
}

冗余连接Ⅱ

685. 冗余连接 II

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1n)的树及一条附加的有向边构成。附加的边包含在 1n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 uivi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

cpp 复制代码
class Solution {
public:

    /*  算法分成三种情况:1:找到入度为2的节点,删除其中的一条边,要注意删除边后剩余的部分依然能构成一颗有向树
            情况2:如果没有入度为2的节点,则说明题目中有环,删除构成环的边即可    
    */
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        // 顶点数 = 边数
        int n = edges.size();
        vector<int> inDegree(n+1,0); 
        // step1: 先统计每个节点的入度
        for(vector<int> edge : edges) {
            inDegree[edge[1]] ++;
        }
        // for(int degree : inDegree) {
        //     cout << degree << endl;
        // }
        // return inDegree;
        // 情况1和2:
        // 记录其中入度为2的边(若有的话就两条边)
        vector<int> edge;
        // 从后往前:因为优先要删除后面的那条边
        for(int i = n-1; i>=0; i--) {
            if(inDegree[edges[i][1]] == 2) { // 这条边后面的入度节点为2
                edge.push_back(i); // edge存入的是要删除边的下标
            }
        }

        // 考虑情况1与2
        if(edge.size() > 0){
            if(isTreeAfterRemoveEdge(edges,edge[0])) {
                return edges[edge[0]];
            }else{
                return edges[edge[1]];
            }
        }
        // 情况三 
        // 此时只有入度为1的顶点,即一定会存在有向环,需要找到构成环的边返回
        return getRemoveEdge(edges);
    }

private: 
    vector<int> father = vector<int>(1001,0);
    // 并查集
    void init(int n) {
        for(int i = 1; i <= n; ++i) {
            father[i] = i;
        }
    }
    // 寻找根
    int find(int u) {
        return u == father[u] ? u : father[u] = find(father[u]);
    }
    // 将 v->u 这条边加入并查集
    void join(int u,int v) {
        u = find(u);
        v = find(v);
        if(u == v) return;
        father[v] = u;
    }
    // 判断u与v是否找到同一个根(即是否在同一棵上)
    bool isSame(int u, int v) {
        u = find(u);
        v = find(v);
        return u == v;
    }
    // 删除一条边后判断是不是树
    bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int delEdge) {
        int n = edges.size();
        init(n);
        for(int i=0; i<n ; i++) {
            if(i == delEdge) continue;
            if(isSame(edges[i][0],edges[i][1])) {
                return false; // 构成了有向环,则一定不是树
            }
            join(edges[i][0],edges[i][1]); // 两条边加入并查集
        }
        return true;
    }

    // 在已经是环的情况下找到删除的那边条
    // 如果说一条边的两端点已经在并查集中,那这条边不就是多余的吗
    vector<int> getRemoveEdge(const vector<vector<int>>& edges) {
        int n = edges.size();
        init(n);
        int ans = 0;
        for(int i=0; i<n; i++) {
            if(isSame(edges[i][0],edges[i][1])) { // 构成环了就是要找的边
                ans = i;
                break;
            }else {
                join(edges[i][0], edges[i][1]);
            }
        }
        return edges[ans];
    }
};
相关推荐
Jerry1 小时前
LeetCode 28. 找出字符串中第一个匹配项的下标
算法
Jerry3 小时前
LeetCode 459. 重复的子字符串
算法
海石6 小时前
1500分的题目,确实有实力,不过还是我略胜一筹
算法·leetcode
海石6 小时前
【记忆化搜索】条条大路通AC,走好适合你的那一条,走到后再考虑走得快
算法·leetcode
Jerry7 小时前
LeetCode 151. 反转字符串中的单词
算法
gugucoding8 小时前
31. 【C语言】堆栈与队列的实现
c语言·开发语言·数据结构·链表
ChaoZiLL9 小时前
我的数据结构3——链表(link list)
数据结构·链表
a11177610 小时前
LM 算法迭代过程动画演示(SLAM)
算法
头茬韭菜10 小时前
Context 的生死抉择:四层压缩、截断算法与 Session Memory
算法·ai
Jerry10 小时前
LeetCode 541. 反转字符串 II
算法