冗余连接_力扣684_并查集

文章介绍了如何利用并查集数据结构解决LeetCode上的冗余连接问题,阐述了并查集的概念、结构、操作以及避免退化的优化方法,并提供了初始化、查询、合并和判断元素所属集合的函数示例,最后展示了找到冗余边的代码实现。
摘要由CSDN通过智能技术生成
冗余连接

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

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

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

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

示例 1:

在这里插入图片描述
输入: edges = [[1,2], [1,3], [2,3]]
输出: [2,3]

示例 2:

在这里插入图片描述
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]

提示:
n == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ai < bi <= edges.length
ai != bi
edges 中无重复元素
给定的图是连通的

并查集
1.并查集是什么

并查集是一种用来管理元素分组情况的数据结构。并查集可以高效地进行如下操作。不过需要注意并查集虽然可以进行合并操作,但是却无法进行分割操作。
(1)查询元素a和元素b是否属于同一组。
(2)合并元素a和元素b所在的组。
在这里插入图片描述

2.并查集的结构

并查集是使用树形结构实现的。
在这里插入图片描述
每个元素对应一个节点,每个组对应―棵树。在并查集中,哪个节点是哪个节点的父亲以及树的形状等信息无需多加关注,整体组成一个树形结构才是重要的。
(1)初始化
我们准备n个节点来表示n个元素。最开始时没有边。
在这里插入图片描述
(2)合并
像下图一样,从一个组的根向另一个组的根连边,这样两棵树就变成了一棵树,也就把两个组合并为一个组了。
在这里插入图片描述
(3)查询
为了查询两个节点是否属于同一组,我们需要沿着树向上走,来查询包含这个元素的树的根是谁。如果两个节点走到了同一个根,那么就可以知道它们属于同一组。
在下图中,元素2和元素5都走到了元素1,因此它们属于同一组。另一方面,由于元素7走到的是元素6,因此同元素2或元素5属于不同组。
在这里插入图片描述

3.并查集实现中的注意点

在树形数据结构里,如果发生了退化的情况,那么复杂度就会变得很高。因此,有必要想办法避免退化的发生。在并查集中,只需按照如下方法就可以避免退化。
(1)对于每棵树,记录这棵树的高度(rank)。
(2)合并时如果两棵树的rank不同,那么从rank小的向rank大的连边。
在这里插入图片描述
此外,通过路径压缩,可以使得并查集更加高效。对于每个节点,一旦向上走到了一次根节点,就把这个点到父亲的边改为直接连向根。
在这里插入图片描述
在此之上,不仅仅是所查询的节点,在查询过程中向上经过的所有的节点,都改为直接连到根上。这样再次查询这些节点时,就可以很快知道根是谁了。
在这里插入图片描述
在使用这种简化的方法时,为了简单起见,即使树的高度发生了变化,我们也不修改rank的值。

4.并查集的复杂度

加入了这两个优化之后的并查集效率非常高。对n个元素的并查集进行一次操作的复杂度是O(α(n))。在这里,α(n)是阿克曼 (Ackermann))函数的反函数T。这比O(log(n))还要快。
不过,这是“均摊复杂度”。也就是说,并不是每一次操作都满足这个复杂度,而是多次操作之后平均每一次操作的复杂度是O(α(n))的意思。

var (
    n = 1005
    parent = make([]int,n) //父亲
    rank = make([]int,n) //树的高度
)
//初始化n个元素
func initl(n int){
    for i:=0;i<n;i++{
        parent[i] = i
        rank[i] = 0
    }
}
//查询树的根
func find(x int) int{
    if parent[x] == x{
        return x
    }else{
        parent[x] = find(parent[x])
        return parent[x]
    }
}
//合并x和y所属的集合
func unite(x,y int){
    x = find(x)
    y = find(y)
    if x==y{
        return
    }
    if rank[x]<rank[y]{
        parent[x] = y
    }else{
        parent[y] = x
        if rank[x] == rank[y]{
            rank[x]++
        }
    }
}
//判断x和y是否属于同一个集合
func same(x,y int) bool{
    return find(x) == find(y)
}

func findRedundantConnection(edges [][]int) []int {
    initl(n)
    for i:=0;i<len(edges);i++{
        if same(edges[i][0],edges[i][1]){
            return edges[i]
        }else{
            unite(edges[i][0],edges[i][1])
        }
    }
    return []int{}
}

并查集部分为《挑战程序设计竞赛(第2版)》中的内容,做个笔记,如有错误欢迎大家指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值