当前位置: 首页 > article >正文

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

并查集

并查集主要有三个功能。

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

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

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

寻找图中是否存在路径 

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

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

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

给你数组 edges 和整数 nsource 和 destination,如果从 source 到 destination 存在 有效路径 ,则返回 true,否则返回 false 。

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) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。

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

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 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

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

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

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];
    }
};


http://www.kler.cn/a/373380.html

相关文章:

  • 前端代码注释
  • 转移概率矩阵的计算
  • 【简道云 -注册/登录安全分析报告】
  • 如何有效提升MySQL大表分页查询效率(本文以一张900万条数据体量的表为例进行详细解读)
  • 2-134 基于matlab的图像边缘检测
  • 【MATLAB源码-第272期】基于matlab的OMP算法的毫米波MIMO通信系统的混合波束成形仿真。
  • CentOS9 Stream 支持输入中文
  • React中管理state的方式
  • Java EasyExcel 导出报内存溢出如何解决
  • 知识的定义与分类体系详解 - 从零基础到专业理解
  • 【三十八】【QT开发应用】vlcplayer视频播放器(一)实现视频播放,视频暂停,视频停止,进度条调节,音量调节,时长显示功能
  • Qt 坐标系统与坐标变换
  • 外键的作用和用法
  • IPD新产品立项管理的典型问题分析(上)
  • 【JVM 深入了解】JVM 到底包含什么?
  • Pycharm报错:Error:failed to find libmagic. Check your installation
  • 信息学奥赛复赛复习19-CSP-J2023-02公路-贪心算法、向上取整、向下取整
  • C# 支持三种方式实现创建 XML文档
  • 关于Android Studio Koala Feature Drop | 2024.1.2下载不了插件的解决办法
  • PHP反序列化-pikachu
  • JavaEE 多线程第四节 (线程核心操作----线程开始/线程终止)
  • 【机器学习】线性回归模型
  • Linux系统rpm安装MySQL详细操作步骤
  • 19 Docker容器集群网络架构:二、etcd 集群部署
  • 【Java多线程】8 Java 中的并发设计模式
  • 【K8S系列】Kubernetes 中 NodePort 类型的 Service 无法访问的问题【已解决】