数据结构之并查集

表示法

一个不相交集合森林(树集合)有许多元素组成,每个元素保存一个id,一个父指针,并且在高效算法中,还保存一个size或一个rank值
这些元素的父指针被排列成一个或多个树的形式,每一个树代表一个集合。如果一个元素的父指针没有指向其他元素,那么这个元素就是这棵树的root节点,并且是该集合的代表成员。一个集合可以只由一个元素组成。不管怎样,如果元素有一个父,这个元素被标识为跟随它父指针链向上直到树的root节点代表元素(一个没有父指针的节点)集合的一部分
森林(树集合)可以在内存中被紧凑的表示为数组,父节点可以用他们的索引指示

操作

新增-MakeSet

MakeSet操作通过创建一个唯一ID,一个0的rank,并且父指针指向它自身的新元素。指向它自身的父指针表示它自己集合的代表成员元素
MakeSet操作时间复杂度O(1),所以初始化n个集合的时间复杂度为O(n)。
伪代码:
function MakeSet(x) is
if x is not already present then
add x to the disjoint-set tree
x.parent := x
x.rank := 0
x.size := 1

查找-Find

Find(x)表示沿着父指针链从x节点向树上方查找直到父指针是它自身的root元素。该root元素是x所属集合的代表元素,也可能是它自身。

路径压缩-Path compression

每当对树结构使用find查找时,路径压缩使每个节点指向root节点使树的结构变得单调平整。这是有效的,因为在访问root节点的路径上被访问的每个节点都是同一集合的一部分。扁平树结果不仅加快未来对这些元素的操作,也加快了对引用他们的元素操作。
TarjanVan Leeuwen也开发一种单遍查找算法,在实践中更加高效,同时保留同样最差情况的复杂度:路径拆分和路径二分。

路径二分-Path halving

路径二分使至root节点路径上每个其他(非x节点)节点指向它的祖父节点

路径拆分-Path splitting

路径拆分使至root节点路径上每个节点指向它的祖父节点

伪代码

伪代码(个人理解伪代码的halving与splitting部分写反了)
Path compression

function Find(x)
   if x.parent ≠ x
     x.parent := Find(x.parent)
   return x.parent

Path halving

function Find(x)
   while x.parent ≠ x
     x.parent := x.parent.parent
     x := x.parent
   return x

Path splitting

function Find(x)
   while x.parent ≠ x
     x, x.parent := x.parent, x.parent.parent
   return x

路径压缩可以使用迭代法实现,通过首先找到root节点,然后更新所有的父指针:

function Find(x) is
   root := x
   while root.parent ≠ root
       root := root.parent
   while x.parent ≠ root
        parent := x.parent
        x.parent := root
       x := parent
 return root

路径拆分可以不用多重分配来表示(先求右边的值):

function Find(x)
   while x.parent ≠ x
       next := x.parent
       x.parent := next.parent
       x := next
   return x
or
function Find(x)
   while x.parent ≠ x
       prev := x
       x := x.parent
       prev.parent := x.parent
 return x

合并-Union

Union(x,y)使用查找确定x和y所属树的root节点。如果root节点不同,通过将一个树的root节点附加到另一个树的root节点合并。如果单纯的这样做,例如始终使x称为y的子,树的高度增长为O(n)。To prevent this union by rank or union by size is used.为了避免这样的高度增长合并可以通过使用rank或者size

按等级-by rank

按rank合并始终让较短小的树附加至较高大的树上。因此,合并树的结果集不会比原始树更高,除非两个树是同样的高度,在这种情况下,合并树的结果集会高出一个节点。
为实现按等级合并,每个元素需要关联一个等级。初始化一个集合有一个元素等级为0。如果两个集合被合并并且有相同的等级,合并结果的集合的等级会大一级; 否则,如果两个集合合并并且两个集合的等级不同,合并树的结果的等级是两者中较大的那个集合的等级。等级被用来代替高度或者深度,因为路径压缩会随着时间的推移而改变树的高度。

按大小-by size

按大小合并始终将较少元素的树附加到拥有较多元素的树的root节点

伪代码

伪代码

Union by rank
function Union(x, y) is
     xRoot := Find(x)
     yRoot := Find(y)
 
     if xRoot = yRoot then
         // x与y已经在同一个集合里,直接返回
         return
 
     // x与y不在同一个集合里,那么我们合并他们
     if xRoot.rank < yRoot.rank then
         xRoot, yRoot := yRoot, xRoot // 交换x的根节点与y的根节点
 
     // 合并y根节点至x根节点
     yRoot.parent := xRoot
     if xRoot.rank = yRoot.rank then
         xRoot.rank := xRoot.rank + 1
Union by size
function Union(x, y) is
     xRoot := Find(x)
     yRoot := Find(y)
 
     if xRoot = yRoot then
         // x and y are already in the same set
         return
 
     // x and y are not in same set, so we merge them
     if xRoot.size < yRoot.size then
         xRoot, yRoot := yRoot, xRoot // swap xRoot and yRoot
 
     // merge yRoot into xRoot
     yRoot.parent := xRoot
     xRoot.size := xRoot.size + yRoot.size

应用

  1. 初始化并查集,遍历所有顶点执行MakeSet
  2. 所有的边按照权重由小至大排序
  3. 遍历所有边
  4. 对边的两个顶点执行Find得到两个顶点的root根节点,判断两个节点是否属于同一个集合
  5. 如果边的两点不属于同一个集合,执行合并:Union。并将边加入最小生成树结果集
  6. 如果边的两点属于同一个集合,继续遍历
  7. 直到最短生成树集合中包含了所有顶点,结束循环,返回最小生成树

1
最小生成树,Kruskal算法实现,伪代码

algorithm Kruskal(G) is
    A :=for each v ∈ G.V do
        MAKE-SET(v)
    for each (u, v) in G.E ordered by weight(u, v), increasing do
        if FIND-SET(u) ≠ FIND-SET(v) then
           A := A ∪ {(u, v)}
           UNION(FIND-SET(u), FIND-SET(v))
    return A

原文

https://en.wikipedia.org/wiki/Disjoint-set_data_structure
https://en.wikipedia.org/wiki/Kruskal%27s_algorithm

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值