• 141、★并查集-LeetCode-冗余连接Ⅰ和Ⅱ


    题目:

    树可以看成是一个连通且 无环 的 无向 图。

    给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 ai 和 bi 之间存在一条边。

    请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/redundant-connection

     

    在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

    输入一个有向图,该图由一个有着 n 个节点(节点值不重复,从 1 到 n)的树及一条附加的有向边构成。附加的边包含在 1 到 n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

    结果图是一个以边组成的二维数组 edges 。 每个元素是一对 [ui, vi],用以表示 有向 图中连接顶点 ui 和顶点 vi 的边,其中 ui 是 vi 的一个父节点。

    返回一条能删除的边,使得剩下的图是有 n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/redundant-connection-ii

    思路:

    在一棵树中,边的数量比节点的数量少1!n个节点,就有n-1条边

    ①冗余连接Ⅰ

    1)并查集:用在图论中,无向图!

    拥有多个功能:感觉合并时的顺序从始至终保持一致,需要题目给出的情况也有顺序要求!

    1.初始化(就是给未连接时的各个节点分配根节点,也叫作代表节点),说明了节点属于哪个集合!

    2.查看根节点:也就是找属于哪个节点

    3.合并节点:就是将两个合并为同一个集合,表示已经连接过

    遇到属于同一个集合的两个节点时,直接返回就是结果;

    ②冗余连接Ⅱ:有根树?只有一个根节点,除了根节点,其他节点都有且只有一个父节点,根节点没有父节点

    只有一个入度为0的节点,为根节点;不存在入度为2的节点;除了根节点,其他节点入度为1

    1)思路:

    ①有根树加入一条边,一定会引入一个环!

    ②并且可能会引入冲突(有根树中不存在入度为2的节点,出现这种节点就是出现了冲突);

    所以加入一条新边后会出现三种情况:1. 只出现了环;2.出现了冲突,冲突的边都在环中;3.出现冲突,只有一条边在环中!

    解决方式:1.环只需要使用并查集判断即可;2.删除一条边,即可实现区冲突加去环;3.只有删除环中的冲突边才能既去环又去冲突。

    怎么判断第3种情况呢?

    判断删除边后,图中是否还有环即可;还有环就删除另一个冲突边

    整体的判断方法:最重要的就是并查集判断环,删除边;还有就是判断冲突节点!

    代码:

    1)并查集:时间复杂度O(NlogN)

    有个问题:如果测试用例中[1,2]、[1,3],[2,3|]换成了[1,2]、[3,1],[2,3]是不是就出错了!

    1. class Solution {
    2. public int[] findRedundantConnection(int[][] edges) {
    3. int len = edges.length;
    4. int[] parent = new int[len + 1];//根节点列表,代表的是每个节点的根节点,也就是属于哪个集合
    5. //并查集初始化:初始时,各自属于自己的集合
    6. for(int i = 1;i <= len;i++){
    7. parent[i] = i;//简单的情况:下标对应数字
    8. }
    9. for(int i = 0;i < len;i++){
    10. int[] edge = edges[i];//是一个长度为2的数组
    11. int nums1 = edge[0],nums2 = edge[1];//一个边上的两个节点
    12. if(find(parent,nums1) != find(parent,nums2)){
    13. //两个节点属于不同的集合,可以加一条边;更改根节点
    14. union(parent,nums1,nums2);
    15. }else{
    16. return edge;
    17. }
    18. }
    19. return new int[0];
    20. }
    21. //并查集:合并(也就是放到一个集合中,拥有同一个根节点)
    22. public void union(int[] parent,int index1,int index2){
    23. //连接节点的操作,也就是将节点添加到同一个集合中
    24. //通过使其拥有相同的根节点来表现
    25. //方式1:parent[find(parent,index1)] = find(parent,index2);
    26. //方式2:
    27. int n1 = find(parent,index1);
    28. int n2 = find(parent,index2);
    29. parent[n1] = n2;
    30. }
    31. //查找根节点(根节点是用来判断是不是在同一个集合中);找到了根节点,就可以判断
    32. public int find(int[] parent,int index){
    33. if(parent[index] != index){//下标数字与具体数值对应?
    34. parent[index] = find(parent,parent[index]);//递归查找
    35. }
    36. return parent[index];
    37. }
    38. }

    2)冗余连接Ⅱ:

    1. class Solution {
    2. int parent[];// 记录节点的根节点,类似上一道题划分集合
    3. public int[] findRedundantDirectedConnection(int[][] edges) {
    4. //初始化:
    5. parent = new int[1001];//并查集
    6. int[] in = new int[1001];//辅助找入度为2的节点
    7. int[] res = new int[]{};
    8. //①寻找是否存在入度为2的节点
    9. for(int[] edge : edges){
    10. //指向它两次,就是入度为2
    11. if(++in[edge[1]] == 2){//判断出现了几次
    12. res = edge;
    13. }
    14. }
    15. //如果找到了,res就是一个长度为2的数组
    16. if(res.length != 0){
    17. if(check(edges,res)) return res;
    18. else{//冲突边有两个:
    19. for(int[] e : edges){
    20. if(e[1] == res[1]){
    21. if(check(edges,e)) return e;
    22. }
    23. }
    24. }
    25. }
    26. // 不存在入度为2的节点:按照无向图,判断即可
    27. //初始化
    28. for (int i = 0; i <= 1000; i++) {
    29. parent[i] = i;
    30. }
    31. for (int[] e : edges) {
    32. // 删除加入形成环的边
    33. if (find(parent,e[0]) == find(parent,e[1])) return e;
    34. else union(parent,e[0], e[1]);
    35. }
    36. return new int[0];
    37. }
    38. // 判断有向边构成的图形是否为树
    39. boolean check(int[][] edges, int[] remove) {
    40. // 初始化并查集
    41. for (int i = 0; i <= 1000; i++) {
    42. parent[i] = i;
    43. }
    44. for (int[] e : edges) {
    45. if (Arrays.equals(e, remove)) continue;//直接不比较,不加并查集,就说明删除了该边
    46. // 删除之后构成的图案不为树
    47. if (find(parent,e[0]) == find(parent,e[1])) return false;
    48. else union(parent,e[0], e[1]);
    49. }
    50. return true;
    51. }
    52. //并查集中找根节点
    53. public int find(int[] parent,int index){
    54. if(parent[index] != index){
    55. //如果x对应的不是自己,说明划分了集合!
    56. //找到了划分的新的结合节点后,要判断现在这个还有没有更上一层的父节点
    57. parent[index] = find(parent,parent[index]);
    58. }
    59. return parent[index];
    60. }
    61. //合并
    62. public void union(int[] parent,int index1,int index2){
    63. if(find(parent,index1) != find(parent,index2)){//肯定是初始状态
    64. parent[find(parent,index2)] = parent[index1];
    65. }
    66. }
    67. }
    68. 代码改自LeetCode-阿尼亚

  • 相关阅读:
    最小生成树:Prim和Kruskal算法原理及实现
    漫谈Entity-Component-System
    树递归遍历和Mirrors遍历
    Linux安装Nginx
    随想录一刷Day57——动态规划
    织梦如何用dede:type调用指定一个栏目的描述
    三七互娱,oppo,快手25届暑期实习内推
    JavaSE入门---认识方法
    Oracle共享内存不释放
    TRC丨TRC 艾美捷 伏马菌素B1说明书
  • 原文地址:https://blog.csdn.net/weixin_54532276/article/details/126824074