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

news2024/11/26 2:23:07

并查集

并查集主要有三个功能。

  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.coloradmin.cn/o/2228437.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HarmonyOS 5.0应用开发——文件读写

【高心星出品】 文章目录 文件读写文件操作创建目录删除目录或者文件扫描目录中文件 文本读写写入文本读取文本内容 文件读写文件写入边读边写 文件读写 Core File Kit&#xff08;文件基础服务&#xff09;为开发者提供一套访问和管理应用文件和用户文件的能力。帮助用户更高…

44-RK3588s调试 camera-engine-rkaiq(rkaiq_3A_server)

在RK3588s平台上调试imx415 camera sensor 过程中&#xff0c;已经识别到了camera sensor ID&#xff0c;并且可以拿到raw图和isp处理后的图像&#xff0c;但是isp处理后的图像偏绿&#xff0c;来看查看后台服务发现rkaiq_3A_server没有运行&#xff0c;然后单独运行rkaiq_3A_s…

Linux 宝塔安装(各操作系统命令合集)

由于CentOS官方已全面停止维护CentOS Linux项目&#xff0c;公告指出 CentOS 7和8在2024年6月30日停止技术服务支持&#xff0c;详情见CentOS官方公告。导致CentOS系统源已全面失效&#xff0c;比如安装宝塔等等会出现网络不可达等报错&#xff0c;需要切换源。系统源问题&…

Android 获取OAID

获取OAID 老规矩&#xff0c;直接上&#xff1a; implementation com.huawei.hms:opendevice:6.11.0.300 // 要获取华为vaid 和aaid&#xff0c;还需添加opendevice 依赖implementation(name: oaid_sdk_2.5.0, ext: aar) import android.content.Context; import android.util.…

基于微信小程序的公务员考试信息查询系统+LW示例参考

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

【Android】Kotlin教程(4)

文章目录 1.field2.计算属性3.主构造函数4.次构造函数5.默认参数6.初始化块7.初始化顺序7.延迟初始化lateinit8.惰性初始化 1.field field 关键字通常与属性的自定义 getter 和 setter 一起使用。当你需要为一个属性提供自定义的行为时&#xff0c;可以使用 field 来访问或设置…

可以在线制作的PS网页版来了!

在当今数字化的创意时代&#xff0c;设计领域不断发展与变革&#xff0c;设计师们对于工具的需求也日益多样化和高效化。随着互联网技术的飞速进步&#xff0c;一种全新的设计工具模式应运而生——在线制作的 PS 网页版。它以其独特的优势和便捷性&#xff0c;逐渐成为众多设计…

高德地图如何添加自己店铺的位置信息?

众所周知&#xff0c;创业开店时&#xff0c;地理位置的选择至关重要。一个优越的地理位置不仅能显著提升店铺的可见度&#xff0c;还能有效吸引更多潜在顾客的光顾。而且&#xff0c;为了将店铺的客流量最大化&#xff0c;商家还需在地图平台上准确标注自己的位置信息&#xf…

【黄豆颗粒数据集】黄豆识别 机器视觉 深度学习(含数据集)

一、背景意义 随着全球农业生产的现代化&#xff0c;黄豆&#xff08;大豆&#xff09;作为一种重要的经济作物&#xff0c;广泛用于食品、饲料和工业原料的生产。准确识别和分类黄豆颗粒对于农业生产的管理、质量控制和市场分析具有重要意义。然而&#xff0c;传统的人工分类方…

JavaEE-多线程上

文章目录 线程概述进程/线程多线程的作用JVM关于线程资源的规范关于Java程序的运行原理 并发与并行并发(concurrency)并行(parallellism)并发编程与并行编程 线程的调度策略分时调度模型抢占式调度模型 创建线程线程类分析入门实现线程的第一种方式实现线程的第二种方式 线程的…

论文阅读:三星-TinyClick

《Single-Turn Agent for Empowering GUI Automation》 赋能GUI自动化的单轮代理 摘要 我们介绍了一个用于图形用户界面&#xff08;GUI&#xff09;交互任务的单轮代理&#xff0c;使用了视觉语言模型Florence-2-Base。该代理的主要任务是识别与用户指令相对应的UI元素的屏幕…

Tomcat servlet response关于中文乱码的经验

前言 最近修改老项目项目&#xff0c;使用zuul网关返回的中文内容乱码了&#xff0c;如果使用GBK或者GB2312编码确正常显示&#xff0c;稍微实验了一下&#xff0c;发现里面很多细节&#xff0c;毕竟Springboot对我们做了很多事情&#xff0c;而且当我们使用不同的模式会出现很…

服务器的免密登录和文件传输

在天文学研究中&#xff0c;通常会采用ssh登录服务器&#xff0c;把复杂的计算交给服务器&#xff0c;但是如果你没有进行额外的配置&#xff0c;那么登录服务器&#xff0c;以及和服务器进行文件传输&#xff0c;每次都要输入账号和密码&#xff0c;比较不方便&#xff0c;Win…

Windows Server NTFS磁盘变RAM的处理过程

问题描述 客户服务器的磁盘数据爆满&#xff0c;需要将磁盘进行扩容&#xff0c;因为是虚拟机所以先在虚拟化平台上将原来的磁盘空间改大&#xff0c;再进入系统&#xff0c;在磁盘管理器上将需要扩容的磁盘进行扩展。扩展完后系统报文件系统有问题&#xff0c;扩容的磁盘容量…

No.23 笔记 | WEB安全 - 任意文件漏洞 part 5

本文全面且深入地探讨了文件上传漏洞相关知识。从基础概念出发&#xff0c;清晰地阐述了文件上传漏洞的定义及其产生的本质原因&#xff0c;同时列出了该漏洞成立的必要条件。详细说明了文件上传漏洞可能对服务器控制权、网站安全以及业务运营带来的严重危害。 文中还深入解析了…

[mysql]子查询的概述和分类及单行子查询

子查询引入 查询的基本结构已经给大家了,子查询里面也是有一些新的内容,子查询其实就是在查询中嵌套另一个查询,叫嵌套查询可能大家更容易理解一点..,类似与FOR循环和FOR循环的嵌套,这一章是我们查询的最难的部分,大家 难度是查询的顶峰,多表查询和子查询是非常重要,SQL优化里…

EDA --软件开发之路

之前一直在一家做数据处理的公司&#xff0c;从事c开发&#xff0c;公司业务稳定&#xff0c;项目有忙有闲&#xff0c;时而看下c&#xff0c;数据库&#xff0c;linux相关书籍&#xff0c;后面跳槽到了家eda公司&#xff0c;开始了一段eda开发之路。 eda 是 electric design …

【移动应用开发】使用多媒体--通知/播放音频/视频

目录 一、具体步骤 二、运行截图 1. 开启通知权限 2. 播放音乐 3. 播放视频 三、源代码 1. activity_main.xml 2. activity_video_player.xml 3. activity_notification.xml 4. 一些配置 5. MainActivity 6. VideoPlayerActivity 7. NotificationActivity 8. And…

代码备份管理 —— Git实用操作

目 录 Git那些事版本控制系统git环境搭建运行bashbash命令行git账号全局设置本地仓库的存在远程仓库的存在git管理基本流程git仓库的文件夹常用git命令工作区变为git仓库add命令使用branch命令使用checkout命令使用commit命令使用仓库状态查询代码变更后提交删除或恢复文件管理…

windows下安装及使用labelme

1.进入Anaconda Prompt对话窗口 输入&#xff1a;conda create --namelabelme python3.6 # 创建一个叫labelme的环境 conda create --namelabelme python3.6 2.激活新建的环境&#xff0c;进入 输入&#xff1a;activate labelme #激活环境 activate labelme 3.安装pyqt5 …