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

代码随想录算法训练营第五十天 | 图 | 并查集

Day 50 总结

  • 自己实现中遇到哪些困难
  • 今日收获,记录一下自己的学习时间
    • 15:00 - 16:00
    • 18:00 - 19:45

图论

  • 深度收缩 & 广度搜索
    • BFS, DFS, visited数组, 四个方向
  • 并查集
    • 数组代表链表, 用集合中的一个元素代表集合的根
  • 最小生成树
  • 拓扑排序
  • 最短路径算法

图论基础

    • 二维空间里,多个点之间相互连接
  • 图的种类
    • 有向图
    • 无向图
    • 加权
  • 度 Degree
    • 连接某个节点的边的数量
    • 出度,入度
  • 连通性
    • 节点联通情况
    • 连通图
      • 任意两个节点存在一条路径
    • 强连通图
      • 任务两个节点可以相互到达
    • 连通分量
      • 独立的连通子图(无向图中的极大连通子图)
    • 强连通分量
      • 有向图中极大强连通子图称之为该图的强连通分量
  • 图的构造
    • 邻接表
      • 数组 + 链表
      • 适用于稀疏图, 空间利用率高
    • 邻接矩阵
      • 二维数组
      • 表达简单,检查连接的速度块,适合稠密图
  • 图的遍历方式
    • 深度优先 dfs
    • 广度优先 bfs

并查集

理论基础

  • 并查集的作用
    • 解决连通性的问题,判断两个元素是否在同一个集合里
    • 并查集:1.两个元素添加到一个集合中 2.判断两个元素在不在同一个集合
  • 原理讲解
    • (集合的定义)将两个元素添加到同一个集合中
    • (搜索集合)判断两个元素是否在同一个集合中
    • 一维连通数组
      • father[A] = B,father[B] = C 这样就表述 A 与 B 与 C连通了(有向连通图)
      • 通过数组,以元素为下标,寻找找下一个元素,想链表一样
      • 但是通过数组可以快速定位元素,并进行链表查询
      • 链表的尽头是集合的根 (无所谓根是谁,可以是集合里任意元素)
      • // 将v,u 这条边加入并查集
        void join(int u, int v) {
            u = find(u); // 寻找u的根
            v = find(v); // 寻找v的根
            if (u == v) return; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
            father[v] = u;
        }
      • // 并查集里寻根的过程
        int find(int u) {
            if (u == father[u]) return u; // 如果根就是自己,直接返回
            else return find(father[u]); // 如果根不是自己,就根据数组下标一层一层向下找
        }
      • // 判断 u 和 v是否找到同一个根
        bool isSame(int u, int v) {
            u = find(u);
            v = find(v);
            return u == v;
        }
      • // 并查集初始化
        void init() {
            for (int i = 0; i < n; ++i) {
                father[i] = i;
            }
        }
  • 路径压缩
    • 将链式搜索过程简化为直接指向根元素
    • // 并查集里寻根的过程
      int find(int u) {
          if (u == father[u]) return u;
          else return father[u] = find(father[u]); // 路径压缩
      }

代码模板

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


int n = 1005; // n根据题目中节点数量而定,一般比节点数量大一点就好
vector<int> father = vector<int> (n, 0); // C++里的一种数组结构

// 并查集初始化
void init() {
    for (int i = 0; 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 这条边加入并查集
void join(int u, int v) {
    u = find(u); // 寻找u的根
    v = find(v); // 寻找v的根
    if (u == v) return ; // 如果发现根相同,则说明在一个集合,不用两个节点相连直接返回
    father[v] = u;
}

拓展

  • 按秩(rank)合并,来压缩路径
  • rank表示树的高度,进行多个树的合并
  • 一定是 rank 小的树合入 到 rank大 的树,这样可以保证最后合成的树rank 最小,降低在树上查询的路径长度。

复杂度分析

路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1)。

在第一次查询的时候,相当于是n叉树上从叶子节点到根节点的查询过程,时间复杂度是logn,但路径压缩后,后面的查询操作都是O(1),而 join 函数 和 isSame函数 里涉及的查询操作也是一样的过程

相关题目

107. 寻找存在的路径

题目连接:107. 寻找存在的路径

题目描述:

给定一个包含 n 个节点的无向图中,节点编号从 1 到 n (含 1 和 n )。

你的任务是判断是否有一条从节点 source 出发到节点 destination 的路径存在。

输入:

N 个节点数, M条边 + node1 <---> node2 + startNode, endNode

输出:

存在路径 start -> end = 1, 不存在 = 0

实现思路:

使用邻接矩阵保存图的信息

DFS 从出发点开始进行路径搜索, 一旦搜索到目标节点, 返回True

 也可以使用并查集, 在无向图中, 如果存在一条路径可以通向A到B, 那么他们一定属于一个集合. 反之, 如果在无向图中不存在一条路径连接两个节点, 说明他们在不同的集合分区里面.

但是怎么想要用集合来判断啊, 是因为是无向图, 然后无向图中所有节点都可达, 除非不在一格集合中.

代码实现:
import java.util.Scanner;
public class Main {
    public static void main (String[] args) {
        /* code */
        Scanner in = new Scanner(System.in);

        int N = in.nextInt();
        int M = in.nextInt();
        
        // 构建邻接矩阵
        int[][] graph = new int[N][N];
        for (int i=0; i<M; i++) {
            int n1 = in.nextInt()-1;
            int n2 = in.nextInt()-1;
            graph[n1][n2] = 1;
            graph[n2][n1] = 1;
        }

        int startNode = in.nextInt()-1;
        int endNode = in.nextInt()-1;
        boolean[] visited = new boolean[N];
        // dfs进行路径搜索
        int result = dfs(graph, startNode, endNode, visited, N);
        System.out.println(result);
    }

    public static int dfs(int[][] graph, int node, int endNode, boolean[] visited, int N) {
        if (node == endNode) return 1;
        visited[node] = true;
        for (int i=0; i<N; i++) {
            if (!visited[i] && graph[node][i] == 1) {
                if (dfs(graph, i, endNode, visited, N) == 1) return 1;
            }
        }
        return 0;
    }
}

并查集使用

public class Main {
    public static void main (String[] args) {
        /* code */
        Scanner in = new Scanner(System.in);

        int N = in.nextInt();
        int M = in.nextInt();
        
        // 并查集
        int[] father = new int[N];
        init(father);

        for (int i=0; i<M; i++) {
            int n1 = in.nextInt()-1;
            int n2 = in.nextInt()-1;
            join(father, n1, n2);
        }

        int startNode = in.nextInt()-1;
        int endNode = in.nextInt()-1;
   

        int res = isSame(father, startNode, endNode) ? 1 : 0;
        System.out.println(res);
    }

    public static void init(int[] father) {
        for (int i=0; i<father.length; i++) {
            father[i] = i;
        }
    }

    public static int find(int[] father, int ele) {
        // while (father[ele] != ele) {
        //     ele = father[ele];
        // }
        // return ele;

        // if () return ele;
        // return find(father, father[ele]);
        
        // return father[ele] == ele ? ele : find(father, father[ele]);

        return ele == father[ele] ? ele : (father[ele] = find(father, father[ele]));
    }

    public static boolean isSame(int[] father, int e1, int e2) {
        int f1 = find(father, e1);
        int f2 = find(father, e2);
        return f1 == f2; 
    }

    public static void join(int[] father, int root, int ele) {
        root = find(father, root);
        ele  = find(father, ele);
        if (root == ele) return;
        father[ele] = root;
    }
}

1

题目连接:

题目描述:
 

输入:

输出:

实现思路:
代码实现:

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

相关文章:

  • docker安装windows desktop后打开失败
  • 【论文阅读+复现】High-fidelity Person-centric Subject-to-Image Synthesis
  • 基于STM32的智能家居蓝牙系统(论文+源码)
  • ARP-Batch-Retargeting 部署实战
  • uniapp中rpx和upx的区别
  • 汇编学习笔记
  • fpga系列 HDL:Quartus II PLL (Phase-Locked Loop) IP核 (Quartus II 18.0)
  • Long类型的数据在网络传输的过程中丢失精度
  • Python-基于Pygame的小游戏(滑雪大冒险)(一)
  • 社交电商新风口:短视频交友+自营商城源码运营
  • filecoin boost GraphQL API 查询
  • AI开发 - 用GPT写一个GPT应用的真实案例
  • 在 Webpack 中Plugin有什么作用?Plugin是什么?
  • 华为认证HCIA——数据传输形式,数据封装的基本概念
  • 企业为什么会需要高防IP?
  • Elasticsearch:什么是信息检索?
  • 16.初识接口2.0 C#
  • SSM 电脑配件销售系统设计及 JSP 实现策略详解
  • 代码随想录算法训练营第八天-字符串-344. 反转字符串
  • OpenCV中的识别图片颜色并绘制轮廓
  • 深度解析:推荐系统的进化之路与深度学习革命
  • vue3中的v-model如何自定义修饰符
  • 科技的成就(六十六)
  • 快捷工具网(www.onlinetool7.com)提供Android KeyCode对照表,帮助开发者轻松理解按键事件
  • uniapp中的uni-file-picker组件上传多张图片到服务器
  • C++ Qt 模板函数和函数重载