一、动态连通性(Dynamic Connectivity)

Union-Find 算法(中文称并查集算法)是解决动态连通性(Dynamic Conectivity)问题的一种算法。动态连通性是计算机图论中的一种数据结构,动态维护图结构中相连信息。简单的说就是,图中各个节点之间是否相连、如何将两个节点连接,连接后还剩多少个连通分量。有点像我们的微信朋友圈,在社交网络中,彼此熟悉的人之间组成自己的圈子,熟悉之后就会添加好友,加入新的圈子。微信用户有几亿人,如何快速计算任意两个用户是否同属于一个圈子呢?计算机是如何将两个用户连接起来的呢?整个微信用户共有几个独立的圈子呢?Union-Find就可以解决上述问题。

二、基本概念
结合下面图的例子来了解基本概念:
 
图中8个节点都是独立互不连通的,也就是一共有8个连通分量。

连通是一种等价关系,也就是说具有如下三个性质:

1、自反性:节点pp是连通的。

2、对称性:如果节点pq连通,那么qp也连通。

3、传递性:如果节点pq连通,qr连通,那么pr也连通。

如果将节点1和节点2进行连接,那连通分量就剩余7个,如下图:
如何在计算中实现这些操作呢?
 
class UF:
   def __init__(self,N): #N表示初始化的节点数,也即最初的连通分量数
def union(self,p,q): # 将节点p和q进行连接
def connected(self,p,q): #判断p和q是否连接
def count(): #返回当前的连通分量

除了社交网络中的朋友圈计算,还可以判断编译器同一个变量的不同引用。

Union-Find 算法的关键就在于unionconnected函数的效率。使用什么样的数据结构来实现这种高效率呢?

三、解决思路

用树来表示节点直接的连接,只要是连接的节点都在同一颗树中,多棵树就是多个连通分量,进而组成了整个森林。怎么用森林来表示连通性呢?我们设定树的每个节点都有一个指针指向其父节点,如果是根节点的话,这个指针指向自己。

如果某两个节点被连通,则让其中的(任意)一个节点的根节点接到另一个节点的根节点上,这样,如果节点pq连通的话,它们一定拥有相同的根节点:

class UF:
def __init__(self,N): #N表示初始化的节点数,也即最初的连通分量数
self.count=N
self.root=[0] #root表示存储每个节点的根节点,第一个位置用0占位
for i in range(1,N+1): #初始化每个节点的根节点指向自己
self.root.append(i) def union(self,p,q): # 将节点p和q进行连接,让p的根节点指向q节点的根节点即可
if self.connected(p,q):
return;
p_root=self.find(p)
q_root=self.find(q)
self.root[p_root]=q_root
self.count-=1 def find(self,p): #查找节点p的根节点
while p!=self.root[p]:
p=self.root[p]
return p def connected(self,p,q): #判断p和q是否连接
return self.find(p)=self.find(q) def count(): #返回当前的连通分量
return self.count

 算法效率分析:

从上述代码可以看出,union-find算法的效率主要在于find函数上面,因为union和connected两个函数的关键都在查找根节点上面,即find函数。find主要功能就是从某个节点向上遍历到树根,其时间复杂度就是树的高度。我们可能习惯性地认为树的高度就是logN,但这并不一定。logN的高度只存在于平衡二叉树,对于一般的树可能出现极端不平衡的情况,使得树几乎退化成直线链表,树的高度最坏情况下可能变成N,如下图所示:

如果按照上面的情况,左边圈子与右边圈子进行连接的话,每个圈子找到根节点的时间复杂度都是O(N)级别的,对于诸如社交网络这样数据规模巨大的问题,而unionconnected的调用都非常频繁,每次都需要线性时间复杂度,效率就显得比较低下了。其实这个问题就是树不平衡造成的。

四、平衡树

 造成树不平衡的主要原因就是在节点关联的时候,没有考虑树节点的多少,而是直接将p节点的根节点直接关联到q节点的根节点上。
 
如上图所示,第一种关联就是不平衡树,第二种关联就比较好。所以改进的方法就是,在union之前,先判断两个树的大小(节点数量),将小点的树附加到大点的树上。这样,合并后的树的深度不会变得非常大。要判断树的大小,需要引进一个新的数组,size 数组,存放树的大小,初始化的时候 size 各元素都设为 1。
class UF:
def __init__(self,N): #N表示初始化的节点数,也即最初的连通分量数
self.count=N
self.root=[0] #root表示存储每个节点的根节点,第一个位置用0占位
self.size=[0]
for i in range(1,N+1): #初始化每个节点的根节点指向自己,树的大小为1
self.root.append(i)
self.size.append(1) def union(self,p,q): # 将节点p和q进行连接,让p的根节点指向q节点的根节点即可
if self.connected(p,q):
return;
p_root=self.find(p)
q_root=self.find(q)
if size[p_root]<= size[q_root]:
self.root[p_root]=q_root
self.size[q_root]+=self.size[p_root] #p节点数合并到q根节点上
else:
self.root[q_root]=self.root[p_root]
self.size[p_root]=self.size[p_root] #q节点数合并到p根节点上
self.count-=1 def find(self,p): #查找节点p的根节点
while p!=self.root[p]:
p=self.root[p]
return p def connected(self,p,q): #判断p和q是否连接
return self.find(p)=self.find(q) def count(): #返回当前的连通分量
return self.count

 

五、路径压缩(进一步优化find函数)

是不是可以进一步压缩树的高度,加快find函数的查找速度,find的效率提升了,等于union和connected函数效率提升了。

如果是上图这种形式,那查找速度基本就是O(1)级别了。但是一个平衡树一步是不可能压缩到这种形式,可以在find函数中加上一行代码,在每次查找的时候,就可以顺便压缩了路径,将树的高度进一步降低,代码如下:

class UF:
def __init__(self,N): #N表示初始化的节点数,也即最初的连通分量数
self.count=N
self.root=[0] #root表示存储每个节点的根节点,第一个位置用0占位
self.size=[0]
for i in range(1,N+1): #初始化每个节点的根节点指向自己,树的大小为1
self.root.append(i)
self.size.append(1) def union(self,p,q): # 将节点p和q进行连接,让p的根节点指向q节点的根节点即可
p_root=self.find(p)
q_root=self.find(q)
if p_root==q_root:
return
if self.size[p_root]<= self.size[q_root]:
self.root[p_root]=q_root
self.size[q_root]+=self.size[p_root] #p节点数合并到q根节点上
else:
self.root[q_root]=self.root[p_root]
self.size[p_root]=self.size[p_root] #q节点数合并到p根节点上
self.count-=1 def find(self,p): #查找节点p的根节点
while p!=self.root[p]:
self.root[p]=self.root[self.root[p]]#路径压缩,直接把p节点指向其父节点的父节点,其实查找也变成了跳跃查找了。
p=self.root[p]
return p def connected(self,p,q): #判断p和q是否连接
return self.find(p)==self.find(q) def count_func(): #返回当前的连通分量
return self.count

这种思路每调用一次find函数,路径就会压缩一次,直到路径不能压缩为止

看代码不好理解,我们以图示的形式进行展示:

 
可以看出每查找一次跟节点,路径就压缩一次。

Union-Find 并查集算法的更多相关文章

  1. 并查集算法Union-Find的思想、实现以及应用

    并查集算法,也叫Union-Find算法,主要用于解决图论中的动态连通性问题. Union-Find算法类 这里直接给出并查集算法类UnionFind.class,如下: /** * Union-Fi ...

  2. hdu 1232 畅通工程(并查集算法)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1232 畅通工程 Time Limit: 4000/2000 MS (Java/Others)    M ...

  3. <算法><Union Find并查集>

    Intro 想象这样的应用场景:给定一些点,随着程序输入,不断地添加点之间的连通关系(边),整个图的连通关系也在变化.这时候我们如何维护整个图的连通性(即判断任意两个点之间的连通性)呢? 一个比较简单 ...

  4. hdu 1213 How Many Tables(并查集算法)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213 How Many Tables Time Limit: 2000/1000 MS (Java/O ...

  5. 并查集(Union/Find)模板及详解

    概念: 并查集是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题.一些常见的用途有求连通子图.求最小生成树的Kruskal 算法和求最近公共祖先等. 操作: 并查集的基本操作有两个 ...

  6. 并查集(Union-Find) 应用举例 --- 基础篇

    本文是作为上一篇文章 <并查集算法原理和改进> 的后续,焦点主要集中在一些并查集的应用上.材料主要是取自POJ,HDOJ上的一些算法练习题. 首先还是回顾和总结一下关于并查集的几个关键点: ...

  7. 【lazy标记得思想】HDU3635 详细学习并查集

    部分内容摘自以下大佬的博客,感谢他们! http://blog.csdn.net/dm_vincent/article/details/7769159 http://blog.csdn.net/dm_ ...

  8. LeetCode:并查集

    并查集 这部分主要是学习了 labuladong 公众号中对于并查集的讲解,文章链接如下: Union-Find 并查集算法详解 Union-Find 算法怎么应用? 概述 并查集用于解决图论中「动态 ...

  9. HDU 1863 畅通工程 (并查集)

    原题链接:畅通工程 题目分析:典型的并查集模版题,这里就不详细叙述了.对算法本身不太了解的可以参考这篇文章:并查集算法详解 代码如下: #include <iostream> #inclu ...

随机推荐

  1. Linux内存管理(最透彻的一篇)【转】

    转自:https://www.cnblogs.com/ralap7/p/9184773.html 摘要:本章首先以应用程序开发者的角度审视Linux的进程内存管理,在此基础上逐步深入到内核中讨论系统物 ...

  2. adb命令之解锁打卡

    adb devicesadb shell input keyevent 26                       按手机电源键adb shell input swipe 400 1080 40 ...

  3. 201871010136-赵艳强《面向对象程序设计(java)》第四周学习总结

    项目 内容 这个作业属于哪个课程 <任课教师博客主页链接>https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 <作业链接地址>http ...

  4. Spring事务异常rollback-only 笔记

    造成以上异常的原因情形: 在spring里面我们配置了事务的传播机制是REQUIRED,所以这两个事务最终会合并成一个事务.当a方法调用b方法时,程序中a方法中由于某某原因导致抛出异常(或者明确将该事 ...

  5. Codeforces Round #549 (Div. 2) E 倍增处理按排列顺序的上一个位置

    https://codeforces.com/contest/1143/problem/E 题意 p为n的一个排列,给出有m个数字的数组a,q次询问,每次询问a数组区间[l,r]中是否存在子序列为p的 ...

  6. Codeforces.1029D.Isolation(DP 分块)

    题目链接 \(Description\) 给定长为\(n\)的序列\(A_i\)和一个整数\(K\).把它划分成若干段,满足每段中恰好出现过一次的数的个数\(\leq K\).求方案数. \(K\le ...

  7. [清华集训2017]小 Y 和地铁(神奇思路,搜索,剪枝,树状数组)

    世界上最不缺的就是好题. 首先考虑暴搜.(还有什么题是从这东西推到正解的……) 首先单独一个换乘站明显没用,只用考虑一对对的换乘站. 那么有八种情况:(从题解偷图)         然后大力枚举每个换 ...

  8. [LeetCode] 255. Verify Preorder Sequence in Binary Search Tree 验证二叉搜索树的先序序列

    Given an array of numbers, verify whether it is the correct preorder traversal sequence of a binary ...

  9. 公司ES升级带来的坑怎么填?

    前言 公司的ES最近需要全部进行升级,目的是方便维护和统一管理.以前的版本不统一,这次准备统一升级到一个固定的版本. 同时还会给ES加上权限控制,虽然都是部署在内网,为了防止误操作,加上权限还是有必要 ...

  10. php和jquery生成QR Code

    php生产QR Code 下载qrcode源码,地址:https://sourceforge.net/projects/phpqrcode/files/releases/ 1.解压后引入qrlib.p ...