冗余连接问题

简介: 本节介绍如何使用并查集解决数据结构中的冗余连接问题。主要内容包括:树与图的区别,冗余连接问题的定义,以及利用并查集判断并找出图中多余的边。文中提供了 C、Java 和 Python 的完整实现代码,并通过示例详细讲解了解决过程。

接触过数据结构的读者都知道,数据结构里囊括了很多种存储数据的方案,包括线性表、树、图等。线性表的特征非常明显,很容易辨识,树和图是初学者经常混淆的。


在数据结构中,所有的树都可以看做是图型结构,但反过来,只有「连通且不存在环路的无向图」可以看做是树型结构。例如,观察下方哪个是树型结构,哪个是图型结构?


图 1 树和图的区别


图 1 中,c) 是有向图,无法被看做是树型结构。重点观察 a) 和 b),它们都是无向图,并且各自包含的顶点都是相互连通的,唯一的不同在于,b) 中存在 1-0-2-1 这条环路,而 a) 中不存在环路,因此 a) 既可以看做是一张图,也可以看做是一棵树;而 b) 只能看做是一张图。


本节要带领大家解决的冗余连接问题,指的是给定一张图型结构,它是由 n 个顶点的树外加一条边构成的,要求找出这条冗余的边。图中所有边的信息都记录在长度为 n 的二维数组 edges 里,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。如果有多条符合条件的边,则返回 edges 数组中最后出现的那个。


举个简单的例子,从下图中找到冗余的那条边。


图 2 给定的图


观察图 2 不难发现,冗余的边可以是 [1, 2]、[1, 3]、[2, 3] 中的任意一条,删除它们中的一个,剩下的就是一棵树。当然多条符合条件的边时,题目要求返回 edges 数组中最后出现的那个,也就是 [2, 3] 这条边。


冗余连接问题可以用并查集解决,接下来给大家讲解具体的实现过程。

并查集解决冗余连接问题

并查集解决冗余连接问题的思路是,依次判断 edges 数组中存储的各个边是否是冗余的,判断依据是:如果当前边两端的顶点不属于一个集合,就将它们合并为一个集合;反之,如果当前边两端的顶点属于同一个集合,表明两个顶点先前已经连通过了,再添加当前边会导致出现环路,因此当前边就是冗余的。


仍以图 2 为例,并查集解决冗余连接问题的过程是:

  • 初始状态下,顶点 1、2、3 各自属于不同的集合;
  • 检查 [1, 2] 边是否冗余:顶点 1 和 2 位于不同的集合里,因此 [1, 2] 不是冗余的,将顶点 1 和 2 合并为同一个集合;
  • 检查 [1, 3] 边是否冗余:顶点 1 和 3 位于不同的集合里,因此 [1, 3] 不是冗余的,将顶点 1 和 3 合并为同一个集合;
  • 检查 [2, 3] 边是否冗余:顶点 2 和 3 位于同一个集合里,因此 [2, 3] 是冗余的。


下面是用并查集解决冗余问题的 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. int* findRedundantConnection(int(*edgs)[2], int n) {
  36. UnionFind uf;
  37. initialize(&uf, n);
  38. // 遍历所有的边
  39. for (int  i = 0; i < n; i++)
  40. {
  41. //判断当前边是否冗余
  42. if (find(&uf, edgs[i][0]) == find(&uf, edgs[i][1])) {
  43. return edgs[i];
  44. }
  45. else
  46. {
  47. unionSets(&uf, edgs[i][0], edgs[i][1]);
  48. }
  49. }
  50. }

  51. int main() {
  52. int edgs[3][2] = { {1,2},{1,3},{2,3} };

  53. //找到冗余的边
  54. int * ret = findRedundantConnection(edgs, 3);
  55. printf("冗余的边是:[%d, %d]\n", ret[0], ret[1]);
  56. return 0;
  57. }


下面是用并查集解决冗余问题的 Java 程序:

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

  5. // 构造函数,初始化并查集,每个节点的父节点初始为自己,集合数量为节点个数
  6. public UnionFind(int size) {
  7.        parent = new int[size + 1];
  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. // FindRedundantConnection 类定义(在 FindRedundantConnection.java 文件中)
  35. public class FindRedundantConnection {
  36. // 查找冗余连接的方法
  37. public int[] findRedundantConnection(int[][] edges) {
  38. UnionFind uf = new UnionFind(edges.length);
  39. int[] result = new int[2];
  40. for (int[] edge : edges) {
  41. if (uf.find(edge[0]) == uf.find(edge[1])) {
  42.                result[0] = edge[0];
  43.                result[1] = edge[1];
  44. } else {
  45.                uf.unionSets(edge[0], edge[1]);
  46. }
  47. }
  48. return result;
  49. }

  50. public static void main(String[] args) {
  51. int[][] edges = { {1, 2}, {1, 3}, {2, 3} };

  52. FindRedundantConnection validPath = new FindRedundantConnection();
  53. int[] result = validPath.findRedundantConnection(edges);

  54.        System.out.println("冗余的边是:[" + result[0] + ", " + result[1] + "]");
  55. }
  56. }


下面是用并查集解决冗余问题的 Python 程序:

  1. class UnionFind:
  2. def __init__(self, size):
  3.        self.parent = [i for i in range(size + 1)]
  4.        self.count = size

  5. # 初始化并查集
  6. def initialize(self, size):
  7. for i in range(size + 1):
  8.            self.parent[i] = i
  9.        self.count = size

  10. # 查找操作,返回元素所在集合的代表元素
  11. def find(self, x):
  12. if self.parent[x] != x:
  13.            self.parent[x] = self.find(self.parent[x])  # 路径压缩
  14. return self.parent[x]

  15. # 合并两个集合
  16. def union_sets(self, x, y):
  17.        x_root = self.find(x)
  18.        y_root = self.find(y)
  19. if x_root != y_root:
  20.            self.parent[x_root] = y_root
  21.            self.count -= 1

  22. # 查找冗余连接的方法
  23. def find_redundant_connection(edges):
  24.    uf = UnionFind(len(edges))
  25.    result = [0, 0]
  26. for edge in edges:
  27. if uf.find(edge[0]) == uf.find(edge[1]):
  28.            result[0], result[1] = edge[0], edge[1]
  29. else:
  30.            uf.union_sets(edge[0], edge[1])
  31. return result


  32. if __name__ == "__main__":
  33.    edges = [[1, 2], [1, 3], [2, 3]]

  34.    ret = find_redundant_connection(edges)
  35. print(f"冗余的边是:[{ret[0]}, {ret[1]}]")


运行程序,输出结果为:

冗余的边是:[2, 3]接触过数据结构的读者都知道,数据结构里囊括了很多种存储数据的方案,包括线性表、树、图等。线性表的特征非常明显,很容易辨识,树和图是初学者经常混淆的。


在数据结构中,所有的树都可以看做是图型结构,但反过来,只有「连通且不存在环路的无向图」可以看做是树型结构。例如,观察下方哪个是树型结构,哪个是图型结构?


图 1 树和图的区别


图 1 中,c) 是有向图,无法被看做是树型结构。重点观察 a) 和 b),它们都是无向图,并且各自包含的顶点都是相互连通的,唯一的不同在于,b) 中存在 1-0-2-1 这条环路,而 a) 中不存在环路,因此 a) 既可以看做是一张图,也可以看做是一棵树;而 b) 只能看做是一张图。


本节要带领大家解决的冗余连接问题,指的是给定一张图型结构,它是由 n 个顶点的树外加一条边构成的,要求找出这条冗余的边。图中所有边的信息都记录在长度为 n 的二维数组 edges 里,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。如果有多条符合条件的边,则返回 edges 数组中最后出现的那个。


举个简单的例子,从下图中找到冗余的那条边。


图 2 给定的图


观察图 2 不难发现,冗余的边可以是 [1, 2]、[1, 3]、[2, 3] 中的任意一条,删除它们中的一个,剩下的就是一棵树。当然多条符合条件的边时,题目要求返回 edges 数组中最后出现的那个,也就是 [2, 3] 这条边。


冗余连接问题可以用并查集解决,接下来给大家讲解具体的实现过程。

并查集解决冗余连接问题

并查集解决冗余连接问题的思路是,依次判断 edges 数组中存储的各个边是否是冗余的,判断依据是:如果当前边两端的顶点不属于一个集合,就将它们合并为一个集合;反之,如果当前边两端的顶点属于同一个集合,表明两个顶点先前已经连通过了,再添加当前边会导致出现环路,因此当前边就是冗余的。


仍以图 2 为例,并查集解决冗余连接问题的过程是:

  • 初始状态下,顶点 1、2、3 各自属于不同的集合;
  • 检查 [1, 2] 边是否冗余:顶点 1 和 2 位于不同的集合里,因此 [1, 2] 不是冗余的,将顶点 1 和 2 合并为同一个集合;
  • 检查 [1, 3] 边是否冗余:顶点 1 和 3 位于不同的集合里,因此 [1, 3] 不是冗余的,将顶点 1 和 3 合并为同一个集合;
  • 检查 [2, 3] 边是否冗余:顶点 2 和 3 位于同一个集合里,因此 [2, 3] 是冗余的。


下面是用并查集解决冗余问题的 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. int* findRedundantConnection(int(*edgs)[2], int n) {
  36. UnionFind uf;
  37. initialize(&uf, n);
  38. // 遍历所有的边
  39. for (int  i = 0; i < n; i++)
  40. {
  41. //判断当前边是否冗余
  42. if (find(&uf, edgs[i][0]) == find(&uf, edgs[i][1])) {
  43. return edgs[i];
  44. }
  45. else
  46. {
  47. unionSets(&uf, edgs[i][0], edgs[i][1]);
  48. }
  49. }
  50. }

  51. int main() {
  52. int edgs[3][2] = { {1,2},{1,3},{2,3} };

  53. //找到冗余的边
  54. int * ret = findRedundantConnection(edgs, 3);
  55. printf("冗余的边是:[%d, %d]\n", ret[0], ret[1]);
  56. return 0;
  57. }


下面是用并查集解决冗余问题的 Java 程序:

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

  5. // 构造函数,初始化并查集,每个节点的父节点初始为自己,集合数量为节点个数
  6. public UnionFind(int size) {
  7.        parent = new int[size + 1];
  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. // FindRedundantConnection 类定义(在 FindRedundantConnection.java 文件中)
  35. public class FindRedundantConnection {
  36. // 查找冗余连接的方法
  37. public int[] findRedundantConnection(int[][] edges) {
  38. UnionFind uf = new UnionFind(edges.length);
  39. int[] result = new int[2];
  40. for (int[] edge : edges) {
  41. if (uf.find(edge[0]) == uf.find(edge[1])) {
  42.                result[0] = edge[0];
  43.                result[1] = edge[1];
  44. } else {
  45.                uf.unionSets(edge[0], edge[1]);
  46. }
  47. }
  48. return result;
  49. }

  50. public static void main(String[] args) {
  51. int[][] edges = { {1, 2}, {1, 3}, {2, 3} };

  52. FindRedundantConnection validPath = new FindRedundantConnection();
  53. int[] result = validPath.findRedundantConnection(edges);

  54.        System.out.println("冗余的边是:[" + result[0] + ", " + result[1] + "]");
  55. }
  56. }


下面是用并查集解决冗余问题的 Python 程序:

  1. class UnionFind:
  2. def __init__(self, size):
  3.        self.parent = [i for i in range(size + 1)]
  4.        self.count = size

  5. # 初始化并查集
  6. def initialize(self, size):
  7. for i in range(size + 1):
  8.            self.parent[i] = i
  9.        self.count = size

  10. # 查找操作,返回元素所在集合的代表元素
  11. def find(self, x):
  12. if self.parent[x] != x:
  13.            self.parent[x] = self.find(self.parent[x])  # 路径压缩
  14. return self.parent[x]

  15. # 合并两个集合
  16. def union_sets(self, x, y):
  17.        x_root = self.find(x)
  18.        y_root = self.find(y)
  19. if x_root != y_root:
  20.            self.parent[x_root] = y_root
  21.            self.count -= 1

  22. # 查找冗余连接的方法
  23. def find_redundant_connection(edges):
  24.    uf = UnionFind(len(edges))
  25.    result = [0, 0]
  26. for edge in edges:
  27. if uf.find(edge[0]) == uf.find(edge[1]):
  28.            result[0], result[1] = edge[0], edge[1]
  29. else:
  30.            uf.union_sets(edge[0], edge[1])
  31. return result


  32. if __name__ == "__main__":
  33.    edges = [[1, 2], [1, 3], [2, 3]]

  34.    ret = find_redundant_connection(edges)
  35. print(f"冗余的边是:[{ret[0]}, {ret[1]}]")


运行程序,输出结果为:

冗余的边是:[2, 3]

相关文章
|
存储 easyexcel Java
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
|
8月前
|
存储 弹性计算 安全
阿里云老用户活动参考:建站套餐、爆款单品、CDN与安全等云产品特惠
阿里云推出老用户专属特惠活动,涵盖建站套餐、爆款单品、CDN安全、存储数据库及AI服务等六大板块。活动提供丰富的组合优惠与单品特惠,如轻量应用服务器低至28元/月,ECS上云套餐首年仅需184元起,云盾证书服务68元/年。此外,通义千问、万相等AI模型抵扣包及企业邮箱等产品同步让利。此次活动精准匹配从中小微建站到企业级扩容的多元需求,多维降低上云成本。
922 4
|
Unix Linux Python
在Python中,删除环境变量
在Python中,删除环境变量
1281 8
|
人工智能 监控 安全
API安全测试工具:数字经济的免疫防线
API安全面临漏洞盲区、配置错误与合规碎片三大挑战,传统手段难抵新型风险。破局需构建智能漏洞探针、配置审计中枢与合规映射引擎三位一体防御矩阵。Burp Suite、Noname Security、Traceable AI与板栗看板等工具助力企业实现自动化检测、精准响应与高效合规,打造API安全免疫体系。
|
9月前
|
人工智能 人机交互 语音技术
输入剧本即可生成漫剧:360 短剧智能体如何落地执行型 AI
2026 年全球将迎来“百亿智能体”时代,这意味着AI从大模型到可以生成能力到可执行能力智能体,360宣布即将发布一款 短剧智能体,即用户只需输入剧本,它就能生成漫剧大片,极大降低影视内容创作门槛。
720 0
|
自然语言处理 并行计算 算法
cp-sat求解器介绍及使用案例
cp-sat求解器介绍及使用案例 更多文章欢迎关注我的微信公众号:Python学习杂记
5520 1
|
Linux Shell 开发工具
Linux Vim批量注释和自定义注释
在Vim中,快速批量注释和取消Shell脚本的多行可以使用替换命令。例如,用`:1,10s/^/#/g`在第1到10行行首加`#`注释,`:1,10s/^#//g`则移除这些行的行首`#`。定义快捷键如`:map^P l#&lt;Esc&gt;`(需用Ctrl+V+P生成^P)能一键在当前行添加`#`注释。要取消注释,可以定义`:map^B 0x`来删除行首字符。通过`.vimrc`保存快捷键设置,可使它们在每次启动Vim时生效。
975 6
|
数据采集 人工智能 自然语言处理
Qwen模型角色扮演最佳实践
角色扮演大模型通过模拟特定角色的行为、语言风格和情感表达,实现高度拟人化和定制化的互动体验。与传统通用模型相比,角色扮演模型在语言风格、性格特征和情绪反应上更加细腻,提供更真实的交互体验。本文介绍了如何通过system prompt、few-shot学习和微调等技术实现大模型的拟人化,包括使用阿里云百炼平台进行角色扮演测试,以及如何通过合成数据和Lora微调提高模型的表演效果。最终,展示了如何通过优化数据质量和训练策略,显著提升角色扮演模型的表现。
|
Ubuntu Linux
Fedora 36 ARM 镜像源更换与软件安装
Fedora 36是Linux发行版,由社区开发,红帽赞助。安装软件通常用DNF(RPM包)。若需安装.deb包,先用alien转换。遇到问题时,可删除`/etc/yum.repo.d`目录内容,改用阿里云镜像源,如: 简而言之,Fedora 36的软件安装涉及DNF或alien,镜像源更换解决安装问题,阿里云镜像提供速度优化。
1210 9
|
决策智能
ortools求解非线性问题
ortools求解非线性问题
1746 0
ortools求解非线性问题