寻找图中是否存在路径

简介: 本节介绍了如何使用并查集判断图中两个顶点之间是否存在有效路径。通过将图的边信息存储到并查集中,同一连通区域的顶点会处于同一集合。只需比较两个顶点的根节点是否相同,即可判断它们是否连通。文中提供了C语言、Java和Python的实现代码,并通过示例验证了算法的正确性。

我们在《并查集》一节详细介绍了并查集这种存储结构,本节趁热打铁,带领读者用并查集解决一个实际问题,即判断图中指定的两个顶点之间是否存在有效的路径。


举个简单的例子,观察下图:


图 1 包含 6 个顶点的图


这是一张包含 6 个顶点的图,其中顶点 1 和顶点 2 是连通的,它们之间存在的有效路径是1-0-2;而顶点 1 和 顶点 5 之间是不连通的,它们之间不存在有效的路径。  

并查集判断图中是否存在路径

判断图中指定的两个顶点之间是否存在有效路径,用并查集解决此问题的思路是:将整张图存储到并查集中,图中相互连接的各个顶点同处一个集合,它们的根结点是相同的。也就是说,对于图中指定的两个顶点,如果他们的根结点相同,就表明它们是连通的,它们之间就一定存在有效的路径;反之如果它们的根结点不同,表明它们处于不同的集合,它们不是连通的,它们之间就不存在有效的路径。


仍以图 1 为例,把整张图(n = 6, edges = [ [0,1], [0,2], [3,5], [5,4], [4,3] ])存储到并查集中,最终并查集的存储状态如下图所示:


图 2 存储图的并查集


可以看到,下标 0 、1 和 2 位置上存储的根结点都是 0,表明顶点 0、1 和 2 同处一个集合,它们之间是相互连通的;同理,下标 3、4 和 5 位置上存储的根结点都是 3,表明顶点 3、4 和 5 同处一个集合,它们之间是相互连通的。


判断图中两个顶点之间是否存在有效的路径,只需要分别找到两个顶点的根结点,如果它们的根结点相同,表明它们同处一个集合,它们之间就至少存在一条有效的路径;反之如果根结点不同,表明它们身处不同的集合,它们之间不存在有效的路径。

并查集解决图中是否存在路径

下面是用并查集解决“图中是否存在路径”的C语言程序:

  1. #include <stdio.h>
  2. #include <stdlib.h>

  3. typedef enum { false, true } bool;
  4. #define MAX_SIZE 1000  // 可以根据需要调整最大尺寸

  5. // 并查集数组
  6. typedef struct {
  7. int parent[MAX_SIZE];
  8. int count;
  9. }UnionFind;

  10. // 初始化并查集
  11. void initialize(UnionFind* uf, int size) {
  12. for (int i = 0; i < size; i++) {
  13.        uf->parent[i] = i;
  14. }
  15.    uf->count = size;
  16. }

  17. // 查找根节点(代表元素)
  18. int find(UnionFind* uf, int x) {
  19. if (uf->parent[x] != x) {
  20.        uf->parent[x] = find(uf, uf->parent[x]); // 路径压缩
  21. }
  22. return uf->parent[x];
  23. }

  24. // 合并两个元素所在的集合
  25. void unionSets(UnionFind* uf, int x, int y) {
  26. //找到两个元素所在集合的代表元素
  27. int xRoot = find(uf, x);
  28. int yRoot = find(uf, y);
  29. //如果代表元素不同,表明它们是两个集合,将它们合并
  30. if (xRoot != yRoot) {
  31.        uf->parent[xRoot] = yRoot; // uf->parent[yRoot] = xRoot;
  32.        uf->count--;
  33. }
  34. }

  35. //判断图中是否存在路径,n 表示图中顶点的数量,edgs数组存储的是图中所有的边,len 表示边的数量,source 和 destination 表示要进行判断的两个顶点
  36. bool validPath(int n, int(*edgs)[2], int len, int source, int destination) {
  37. UnionFind uf;
  38. initialize(&uf, n);
  39. // 把图存储到并查集中
  40. for (int  i = 0; i < len; i++)
  41. {
  42. unionSets(&uf, edgs[i][0], edgs[i][1]);
  43. }
  44. //判断 source 和 destination 是否同处一个集合
  45. if (find(&uf, source) == find(&uf, destination)) {
  46. return true;
  47. }
  48. else
  49. {
  50. return false;
  51. }
  52. }

  53. // 主函数
  54. int main() {
  55. int edgs[5][2] = { {0,1},{0,2},{3,5},{5,4},{4,3} };

  56. //判断顶点 1 和 2 之间是否存在有效的路径
  57. bool ret = validPath(6, edgs, 5, 1, 2);
  58. if (ret == true) {
  59. printf("顶点 1 和顶点 2 之间存在有效的路径\n");
  60. }
  61. else
  62. {
  63. printf("顶点 1 和顶点 2 不存在有效的路径\n");
  64. }

  65. //判断顶点 1 和 5 之间是否存在有效的路径
  66.    ret = validPath(6, edgs, 5, 1, 5);
  67. if (ret == true) {
  68. printf("顶点 1 和顶点 5 之间存在有效的路径\n");
  69. }
  70. else
  71. {
  72. printf("顶点 1 和顶点 5 不存在有效的路径\n");
  73. }

  74. return 0;
  75. }


下面是用并查集解决“图中是否存在路径”的 Java 程序:

  1. // 并查集类,用于处理集合合并和查找操作
  2. public class UnionFind {
  3. private int[] parent;  // 记录节点的父节点
  4. private int count;     // 记录集合的数量

  5. // 构造函数,初始化并查集,每个节点的父节点初始为自己,集合数量为节点个数
  6. public UnionFind(int size) {
  7.        parent = new int[size];
  8. for (int i = 0; i < size; i++) {
  9.            parent[i] = i;
  10. }
  11.        count = size;
  12. }

  13. // 查找根节点(代表元素)的方法,同时进行路径压缩
  14. public int find(int x) {
  15. if (parent[x] != x) {
  16.            parent[x] = find(parent[x]);  // 路径压缩
  17. }
  18. return parent[x];
  19. }

  20. // 合并两个元素所在的集合
  21. public void unionSets(int x, int y) {
  22. int xRoot = find(x);
  23. int yRoot = find(y);
  24. if (xRoot != yRoot) {
  25.            parent[xRoot] = yRoot;  // 合并集合
  26.            count--;
  27. }
  28. }

  29. // 获取当前集合的数量
  30. public int getCount() {
  31. return count;
  32. }
  33. }

  34. // 主程序类,用于测试并查集是否存在有效路径
  35. public class ValidPath {
  36. // 判断图中是否存在路径的方法
  37. public static boolean validPath(int n, int[][] edges, int len, int source, int destination) {
  38. UnionFind uf = new UnionFind(n);
  39. // 将图存储到并查集中
  40. for (int i = 0; i < len; i++) {
  41.            uf.unionSets(edges[i][0], edges[i][1]);
  42. }
  43. // 判断 source 和 destination 是否属于同一个集合
  44. return uf.find(source) == uf.find(destination);
  45. }

  46. // 主函数,用于测试并查集是否能够判断有效路径
  47. public static void main(String[] args) {
  48. int[][] edges = {{0, 1}, {0, 2}, {3, 5}, {5, 4}, {4, 3}};

  49. // 判断顶点 1 和 2 之间是否存在有效的路径
  50. boolean ret = validPath(6, edges, 5, 1, 2);
  51. if (ret) {
  52.            System.out.println("顶点 1 和顶点 2 之间存在有效的路径");
  53. } else {
  54.            System.out.println("顶点 1 和顶点 2 不存在有效的路径");
  55. }

  56. // 判断顶点 1 和 5 之间是否存在有效的路径
  57.        ret = validPath(6, edges, 5, 1, 5);
  58. if (ret) {
  59.            System.out.println("顶点 1 和顶点 5 之间存在有效的路径");
  60. } else {
  61.            System.out.println("顶点 1 和顶点 5 不存在有效的路径");
  62. }
  63. }
  64. }


下面是用并查集解决“图中是否存在路径”的 Python 程序:

  1. class UnionFind:
  2. def __init__(self, size):
  3. # 初始化并查集,每个节点的父节点初始为自己,集合数量为节点个数
  4.        self.parent = [i for i in range(size)]
  5.        self.count = size

  6. def find(self, x):
  7. # 查找根节点(代表元素)的方法,同时进行路径压缩
  8. if self.parent[x] != x:
  9.            self.parent[x] = self.find(self.parent[x])  # 路径压缩
  10. return self.parent[x]

  11. def union_sets(self, x, y):
  12. # 合并两个元素所在的集合
  13.        x_root = self.find(x)
  14.        y_root = self.find(y)
  15. if x_root != y_root:
  16.            self.parent[x_root] = y_root  # 合并集合
  17.            self.count -= 1

  18. def get_count(self):
  19. # 获取当前集合的数量
  20. return self.count

  21. def valid_path(n, edges, source, destination):
  22.    uf = UnionFind(n)
  23. # 将图存储到并查集中
  24. for edge in edges:
  25.        uf.union_sets(edge[0], edge[1])
  26. # 判断 source 和 destination 是否属于同一个集合
  27. return uf.find(source) == uf.find(destination)

  28. if __name__ == "__main__":
  29.    edges = [[0, 1], [0, 2], [3, 5], [5, 4], [4, 3]]

  30. # 判断顶点 1 和 2 之间是否存在有效的路径
  31.    ret = valid_path(6, edges, 1, 2)
  32. if ret:
  33. print("顶点 1 和顶点 2 之间存在有效的路径")
  34. else:
  35. print("顶点 1 和顶点 2 不存在有效的路径")

  36. # 判断顶点 1 和 5 之间是否存在有效的路径
  37.    ret = valid_path(6, edges, 1, 5)
  38. if ret:
  39. print("顶点 1 和顶点 5 之间存在有效的路径")
  40. else:
  41. print("顶点 1 和顶点 5 不存在有效的路径")


运行程序,输出结果为:

顶点 1 和顶点 2 之间存在有效的路径

顶点 1 和顶点 5 不存在有效的路径

相关文章
|
存储 缓存 文件存储
如何保证分布式文件系统的数据一致性
分布式文件系统需要向上层应用提供透明的客户端缓存,从而缓解网络延时现象,更好地支持客户端性能水平扩展,同时也降低对文件服务器的访问压力。当考虑客户端缓存的时候,由于在客户端上引入了多个本地数据副本(Replica),就相应地需要提供客户端对数据访问的全局数据一致性。
33076 80
如何保证分布式文件系统的数据一致性
|
前端开发 容器
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局(上)
HTML5+CSS3前端入门教程---从0开始通过一个商城实例手把手教你学习PC端和移动端页面开发第8章FlexBox布局
17818 24
|
设计模式 存储 监控
设计模式(C++版)
看懂UML类图和时序图30分钟学会UML类图设计原则单一职责原则定义:单一职责原则,所谓职责是指类变化的原因。如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责。而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因。bad case:IPhone类承担了协议管理(Dial、HangUp)、数据传送(Chat)。good case:里式替换原则定义:里氏代换原则(Liskov 
36801 22
设计模式(C++版)
|
存储 编译器 C语言
抽丝剥茧C语言(初阶 下)(下)
抽丝剥茧C语言(初阶 下)
|
机器学习/深度学习 人工智能 自然语言处理
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
带你简单了解Chatgpt背后的秘密:大语言模型所需要条件(数据算法算力)以及其当前阶段的缺点局限性
24873 15
|
机器学习/深度学习 弹性计算 监控
重生之---我测阿里云U1实例(通用算力型)
阿里云产品全线降价的一力作,2023年4月阿里云推出新款通用算力型ECS云服务器Universal实例,该款服务器的真实表现如何?让我先测为敬!
36786 15
重生之---我测阿里云U1实例(通用算力型)
|
SQL 存储 弹性计算
Redis性能高30%,阿里云倚天ECS性能摸底和迁移实践
Redis在倚天ECS环境下与同规格的基于 x86 的 ECS 实例相比,Redis 部署在基于 Yitian 710 的 ECS 上可获得高达 30% 的吞吐量优势。成本方面基于倚天710的G8y实例售价比G7实例低23%,总性价比提高50%;按照相同算法,相对G8a,性价比为1.4倍左右。
|
存储 算法 Java
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的限流器RateLimiter功能服务
随着互联网的快速发展,越来越多的应用程序需要处理大量的请求。如果没有限制,这些请求可能会导致应用程序崩溃或变得不可用。因此,限流器是一种非常重要的技术,可以帮助应用程序控制请求的数量和速率,以保持稳定和可靠的运行。
29925 52

热门文章

最新文章