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

并查集

并查集主要有三个功能。

  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];
    }
};
相关推荐
cxylay20 分钟前
自适应滤波算法分类及详细介绍
算法·分类·自适应滤波算法·自适应滤波·主动噪声控制·anc
茶猫_27 分钟前
力扣面试题 - 40 迷路的机器人 C语言解法
c语言·数据结构·算法·leetcode·机器人·深度优先
轻浮j37 分钟前
Sentinel底层原理以及使用算法
java·算法·sentinel
Abelard_1 小时前
LeetCode--347.前k个高频元素(使用优先队列解决)
java·算法·leetcode
小猪写代码1 小时前
C语言:递归函数(新增)
算法·c#
点云SLAM1 小时前
C++创建文件夹和文件夹下相关操作
开发语言·c++·算法
heeheeai1 小时前
kotlin 函数作为参数
java·算法·kotlin
是十一月末2 小时前
opencv实现KNN算法识别图片数字
人工智能·python·opencv·算法·k-近邻算法
袖清暮雨2 小时前
5_SparkGraphX讲解
大数据·算法·spark
Tisfy2 小时前
LeetCode 3218.切蛋糕的最小总开销 I:记忆化搜索(深度优先搜索DFS)
算法·leetcode·深度优先·题解·记忆化搜索