Union-Find 并查集算法详解

引言

领导(笑呵呵的走过来):有个挑战性但很有意思的项目,你想不想试一下
小溪子:(各个部位有点紧)好的,非常想干。
领导:现在一个手机通常有几个名字,我们已经知道一些名字对代表相同手机,需要设计一个算法能够将一个手机的所有名字连接起来形成一个组,给定任一名字能够查找所属的组,并判断两个名字是否等效(相同手机)。
小溪子:(恰巧看算法)这好像是动态连通性问题。

动态连通性

有以下手机名称,已知(0,1) (0,2), (0,3), (3,4), (5,6), (5,7)代表相同手机。

NONames
0iPhone 8
1Apple A1863
2Apple A1906
3Apple A1905
4Apple A1907
5Oneplus A6003
6Oneplus A6000
7Oneplus 6

为了判断任意两个名称是否指相同手机,将所有手机名称都想象为一个点,每输入一个已知的名称对,将两个点连接在一起,依次输入所有已知名称对,就会形成多个连通图,用来判断任意两个名称是否指相同手机(两点是否连通),绘制为连通图如下:
动态连通

动态连通性还可以应用到网络连接判断,位置间是否有道路相通等。

Union-Find

并差集是一种可以记录一系列节点分别属于哪个子集的数据结构,为了解决动态连通性问题,需要能够实现以下功能:

  • union: 连通输入的一个节点对
  • find: 给定一个节点查找属于哪个连通组
  • is_connected: 判断两个节点是否连通。

实现算法及复杂度比较

Quick-find

利用键值对key-value保存每个节点的组号

No012345678
Group000000111

查找 find(5), find(8)时候,时间复杂度为1。
如果需要union(5, 8),则需要更改每个键值对,所以Union时间复杂度为N
所以键值对只实现了快速查找但合并复杂度高,如果对于大量数据,这是不可以接受的。

Quick-union

为了让Union的效率更高,不是逐一去更改每个节点的组号,需要一个新的数据结构来保存组信息,当合并时候只需更改一个节点,显然树结构可以满足此需要,用根节点保存组号,只要根节点改变,整棵树就修改了。但这样的话,Union提升了但查询效率又下降为树的高度,最坏时候多叉树会变为链表。

Weighted quick-union

为了减少树的高度,在Quick-union算法基础上,当Union时候同时记录每颗树的大小,总是把小树连接到大树,这样能保持树的平衡,树的高度会降低,所以find的效率会提高。
在这里插入图片描述

Weighted quick-union with path compression

在查询时候每次都是从根节点逐一回溯到根节点,如果将所有节点都变成根节点的子节点,树会扁平查询效率高,但这样会增加空间复杂度(反复开辟销毁内存),所以如果只是将节点变成他爷爷的孩子,相当于压缩了查询路径,提高了查询效率。

算法复杂度

算法unionfind
quick-findN1
quick-uniontree heighttree height
weighted quick-unionlgNlgN
weighted quick-union with path compressionvery near to 1very near to 1

Python 完整实现Weighted quick-union with path compression

class UnionFind(object):

    def __init__(self, n_items, compress_path):
        """ Make empty UF with n_items sites, each site initially alone in its own group """
        self.count = n_items                    # capacity, current number of groups
        self.compress_path = compress_path      # shrink tree (speed-up)
        self.parent = list(range(n_items))      # the tree
        self.size = [1] * n_items               # record the size of each subtree/group

    def find(self, p):
        while p != self.parent[p]:
            if self.compress_path:
                self.parent[p] = self.parent[self.parent[p]]
            p = self.parent[p]
        return p

    def union(self, p, q):
        if p == q:
            return
        rootp = self.find(p)
        rootq = self.find(q)
        if rootp == rootq:
            return

        # tree balancing to keep maximum depth smaller
        if self.size[rootp] < self.size[rootq]:
            self.parent[rootp] = rootq
            self.size[rootq] += self.size[rootp]
        else:
            self.parent[rootq] = rootp
            self.size[rootp] += self.size[rootq]
        self.count -= 1

    def is_connected(self, p, q):
        return p == q or self.find(p) == self.find(q)

    def merge_path(self, p):
        steps = [p]
        while p != self.parent[p]:
            p = self.parent[p]
            steps.append(p)
        return steps

代码下载地址:https://gist.github.com/xidongsheng/d69a3af4291f8705a52df2820558d29f

参考文献

[1]https://algs4.cs.princeton.edu/15uf/
[2]https://en.wikipedia.org/wiki/Disjoint-set_data_structure

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值