[L氏并查集] Python 列表法实现非递归并查集,轻松权重优化。

本文介绍了一种新颖的并查集实现方法,该方法采用Python语言,避免了递归和函数调用,通过列表操作实现了高效的数据结构。文章详细解释了如何利用列表的extend方法和元素指向来合并集合,以及如何进行权重优化,以达到更优的时间性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一般的并查集都是用递归或者新建一个类来实现,这里介绍一种用Python来实现的非递归非函数并查集,这个方法暂时未在其他地方见过,尤其是中文领域目前还未见过,很可能是搜索引擎无法搜索到正确内容的原因,所以不排除会有撞车的,不过在撞车警告之前,我都视为是我原创,LeetCode中文版上并查集的题目我基本都写了两种解,在那些题的题解里应该可以看到。(20190730)

 

假设存在n个点存在连通关系connections :

n = 6, connections = [[0, 1], [0, 2], [4, 3], [1, 2], [3, 2]]

求两个点的连通性,就可以使用并查集去检验:

def LsUnionFindSet(self, n: int, connections: List[str], a: int, b: int) -> bool:
    p = [[i] for i in range(n)]       #并查集初始化
    for x, y in connections:          #遍历边
        if p[x] is not p[y]:          #如果两个列表指针不相等
            p[x].extend(p[y])         #将y列表并进x列表
            for z in p[y]:            #遍历y列表的元素
                p[z] = p[x]           #所有y列表的元素都指向x集合
    return p[a] == p[b]

[a, b] 为[0, 4],返回为True

[a, b] 为[2, 5],返回为False

 

 

 

 

时间复杂度应该和常规的并查集一致,每次赋值其实都是指针赋值,相对来说计算量并不大,z层的总循环在我个人多次计数试验应该是在O(n)~O(nlog{}_{2}^{}\textrm{ }(n))这样,符合并查集时间复杂度即阿克曼反函数O(nlog{}_{2}^{}\textrm{ }(n)),具体证明应参考并查集的标准证明,空间复杂度O(n),传递过程大多是传递集合的指针地址,并不增加额外空间。

算上临时空间,最大空间为O(1.5n),即所有元素均分成2块,合并过程中会产生O(0.5n)的临时空间,但合并结束后,临时空间将会被销毁。

同时,这种方法非常容易进行权重优化:

def LsUnionFindSet(self, n: int, connections: List[str], a: int, b: int) -> bool:
    p = [[i] for i in range(n)]       #并查集初始化
    for x, y in connections:          #遍历边
        if p[x] is not p[y]:          #如果两个列表指针不相等
            if len(p[x]) < len(p[y])  #如果x所在的集合小于y所在的集合
                x, y = y, x           #交换x,y使得优先使小集合并入大集合
            p[x].extend(p[y])         #将y列表并进x列表
            for z in p[y]:            #遍历y列表的元素
                p[z] = p[x]           #所有y列表的元素都指向x集合
    return p[a] == p[b]

只需要一个判断就可以让复杂度稳定在O(nlog{}_{2}^{}\textrm{ }(n))以下了,leetcode上用这种方法可以很轻松拿到95-100%的时间成绩。

此方法除了rust,大多数语言都可以轻松实现,无指针语言如java/javascript的代码结构基本跟python一致,有指针的语言如c++/golang等则需要单独处理指针引用,指针安全语言rust则需要包裹智能指针,相对比较麻烦。

下面是静态语言go的链表版实现样例

// 链表
type ListNode struct {
    Val int
    Next *ListNode
}

// 并查集结构,只需要记录链表头尾和大小
type UnionFindSet struct {
    head *ListNode
    tail *ListNode
    size int
}

func LsUnionFindSet(n int, connections [][]int) {
    p := make([]*UnionSet, n)  // 并查集初始化
    for i := range p {
        node := &ListNode{i, nil}
        p[i] = &UnionFindSet {
            head: node,
            tail: node,
            size: 1,
        }
    }
    for _, c := range connections {
        x, y := c[0], c[1]
        if p[x] != p[y] {
            if p[x].size < p[y].size {      // 比较两个并查集尺寸并进行权重优化,O(1)
                x, y = y, x
            }
            p[x].tail.Next = p[y].head      // 大集合的链表尾部连上小集合的链表头部,O(1)
            p[x].tail = p[y].tail           // 更新大集合的链表尾部,O(1)
            p[x].size += p[y].size          // 更新大集合的尺寸,O(1)
            for z := p[y].head; z != nil; z = z.Next {
                p[z.Val] = p[x]             // 遍历小集合,把小集合里所有坐标的集合指向改成大集合
            }                               // O(min(p[x].size, p[y].size))
        } 
    }
}

 

### 加权并查集算法的 Python 实现 加权并查集是一种优化过的并查集结构,它通过记录每棵树的高度或权重来减少查找操作的时间复杂度。相比于普通的 QuickUnion 算法,在每次连接两个集合时,优先将较小的树挂载到较大的树上,从而降低整体树的高度。 以下是基于路径压缩和按秩合并策略的加权并查集算法的 Python 实现: #### 代码实现 ```python class WeightedUnionFind: def __init__(self, size): self.parent = list(range(size)) # 初始化父节点数组,每个节点指向自己 self.rank = [1] * size # 初始化秩数组,表示树的高度(初始为1) def find(self, node): """ 查找节点所属集合的根节点,并进行路径压缩 """ if self.parent[node] != node: self.parent[node] = self.find(self.parent[node]) # 路径压缩 return self.parent[node] def union(self, u, v): """ 合并两个节点所在的集合 """ root_u = self.find(u) root_v = self.find(v) if root_u == root_v: return False # 如果已经在同一个集合中,则无需合并 # 按秩合并:将较小的树挂接到较大的树下 if self.rank[root_u] > self.rank[root_v]: self.parent[root_v] = root_u elif self.rank[root_u] < self.rank[root_v]: self.parent[root_u] = root_v else: self.parent[root_v] = root_u self.rank[root_u] += 1 # 当两棵树高度相同时,增加新树的高度 return True def connected(self, u, v): """ 判断两个节点是否属于同一集合 """ return self.find(u) == self.find(v) ``` #### 使用示例 以下是一个简单的例子展示如何使用上述类完成基本的操作: ```python uf = WeightedUnionFind(10) # 创建大小为10的加权并查集实例 print(f"Initially, is 1 and 2 connected? {uf.connected(1, 2)}") # 输出: False uf.union(1, 2) # 将节点1和节点2连通 print(f"After union(1, 2), is 1 and 2 connected? {uf.connected(1, 2)}") # 输出: True uf.union(2, 3) # 连接节点2和节点3 print(f"After union(2, 3), is 1 and 3 connected? {uf.connected(1, 3)}") # 输出: True ``` 此实现在初始化阶段创建了一个 `parent` 数组用于存储每个节点的父亲节点以及一个 `rank` 数组用来保存各子树的高度信息[^3]。在执行 `union` 方法的过程中采用了按秩合并的方式以保持较低的整体时间开销;而在调用 `find` 方法期间则实现了路径压缩技术进一步提升效率。 #### 性能分析 由于引入了路径压缩与按秩合并两种机制,理论上该版本的并查集可以达到接近常数级 O(α(n)) 的单次操作均摊时间复杂度,其中 α 表示反阿克曼函数,增长极其缓慢,对于实际应用中的输入规模几乎可视为固定值。 --- ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值