寻找图中是否存在路径

简介: 本节介绍了如何使用并查集判断图中两个顶点之间是否存在有效路径。通过将图的边信息存储到并查集中,同一连通区域的顶点会处于同一集合。只需比较两个顶点的根节点是否相同,即可判断它们是否连通。文中提供了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 不存在有效的路径

相关文章
|
4月前
|
算法 Java C语言
弗洛伊德算法求最短路径
弗洛伊德算法用于寻找加权图中各顶点间的最短路径,适用于无向图和有向图。算法基于动态规划思想,通过枚举中间顶点来更新路径,确保最终得到最短路径。该算法要求路径权值非负,否则可能出错。
472 0
|
4月前
|
存储 算法 Java
求数组中的最大值和最小值
本文介绍了在程序中如何查找数组中的最大值和最小值,重点讲解了两种算法:普通算法和分治算法。普通算法通过遍历数组直接比较元素大小,找出最值;而分治算法则通过递归将数组划分成更小的部分,分别找出各部分的最大值,最终合并结果得到整个数组的最大值。文章以 {3,7,2,1} 为例,详细演示了两种算法的实现过程,并提供了 C、Java 和 Python 的代码示例。
311 0
|
4月前
|
存储 搜索推荐 算法
归并排序算法
归并排序是一种基于分治思想的高效排序算法,通过将序列不断划分为不可再分的子序列,再两两合并完成排序。其时间复杂度为O(nlogn),适用于对序列进行升序或降序排列。
323 0
|
4月前
|
算法 搜索推荐
分治算法的基本思想
分治算法通过将复杂问题拆分成多个独立的小问题,逐一解决后再合并结果。适合处理大规模数据,常见应用包括排序、查找最大值/最小值及汉诺塔等问题。
141 0
|
4月前
|
算法 Java 定位技术
迷宫问题
迷宫问题是指在给定区域内寻找从起点到终点的可行路径。可以使用回溯算法解决,通过不断尝试四个方向(上下左右)移动,若无法前进则回退,直到找到终点或遍历所有可能路径。文中还给出了C语言、Java和Python的实现代码,并展示了运行结果。
196 0
|
4月前
|
算法 程序员
时间复杂度和空间复杂度的概念
本文介绍了如何评估算法的执行效率和内存占用,重点讲解了时间复杂度和空间复杂度的概念及其计算方法。通过大O记法,可以量化算法的运行时间和内存使用情况,从而在不同算法间做出合理选择。
151 0
|
4月前
|
存储 算法
最小生成树的概念与思想
数据结构中的图存储结构包含顶点和边,分为连通图与非连通图。生成树是包含所有顶点、任意两点间仅有一条通路的极小连通图。最小生成树则是权值总和最小的生成树,常用于解决道路建设等实际问题,常用算法有普里姆算法和克鲁斯卡尔算法。
120 0
|
4月前
|
算法 Java C语言
汉诺塔问题
汉诺塔问题源自印度古老传说,涉及将一组圆盘从一根柱子移动到另一根,遵循特定规则。文章详细介绍了该问题的背景、解决思路以及如何使用分治算法实现,同时提供了C语言、Java和Python的代码示例。
310 0
|
4月前
|
算法
回溯算法的基本思想
本节介绍回溯算法,通过图1中从A到K的路径查找示例,说明其与穷举法的异同。回溯算法通过“回退”机制高效试探各种路径,适用于决策、优化和枚举问题。
133 0