并查集

并查集(Union-Find)算法介绍

こ雲淡風輕ζ 提交于 2020-01-07 14:14:00
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> 本文主要介绍解决 ‍ 本文主要介绍解决动态连通性一类问题的一种算法,使用到了一种叫做并查集的数据结构,称为Union-Find。更多的信息可以参考Algorithms 一书的Section 1.5,实际上本文也就是基于它的一篇读后感吧。原文中更多的是给出一些结论,我尝试给出一些思路上的过程,即为什么要使用这个方法,而不是别的什么方法。我觉得这个可能更加有意义一些,相比于记下一些结论。 ‍ 关于动态连通性 我们看一张图来了解一下什么是动态连通性: 假设我们输入了一组整数对,即上图中的(4, 3) (3, 8)等等,每对整数代表这两个points/sites是连通的。那么随着数据的不断输入,整个图的连通性也会发生变化,从上图中可以很清晰的发现这一点。同时,对于已经处于连通状态的points/sites,直接忽略,比如上图中的(8, 9)。 动态连通性的应用场景: 网络连接判断: 如果每个pair中的两个整数分别代表一个网络节点,那么该pair就是用来表示这两个节点是需要连通的。那么为所有的pairs建立了动态连通图后,就能够尽可能少的减少布线的需要,因为已经连通的两个节点会被直接忽略掉。 变量名等同性(类似于指针的概念): 在程序中,可以声明多个引用来指向同一对象

POJ -- 1182 食物链

纵饮孤独 提交于 2020-01-07 05:02:27
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> 题目网址 : POJ -- 1182 这是一道利用并查集模拟的题目,也是并查集的裸题。 乍一看好像有点麻烦,因为会觉得动物们来回吃来吃去情况就会变得越来越复杂。 其实无论X和Y是同类还是捕食关系,归根结底都是集合合并的问题(捕食关系“错位”一下也是对等合并)。 考虑什么时候会发生冲突?冲突的情况只有一个,那就是两个有交集的食物链合并,并且合并的时候发生错位,也就是ABC不对等合并。另外的情况都是元素X所在的食物链和元素Y所在的食物链没有交集,这样合并后更新就好。 举个例子,就是你可以ABC和ABC合并, 也可以ABC和DEF合并,但是绝对不能ABC和BCA或者ACB合并。 原因是你现在获取了两个元素X和Y,他们俩要么处于同一个食物链中,要么处于不同食物链中,不同食物链可以直接合并,相同食物链只能对等合并(其实也就是不用合并),不能错位合并。 并查集是一种树结构,有一个特别典型的特性:只要不是根节点,就先获取根节点再做处理。 其实看过并查集的实现就会发现,如果一个节点不是根节点,那么跟这个节点有关的信息都是没意义的。因为并查集只保证根节点的信息具有时效性,其他非根节点的节点都只有一个作用,那就是找到根节点。 贴下代码,这个模拟的过程还是比较恶心人的。 #include <cstdio> #include

并查集之通过根节点实现

邮差的信 提交于 2020-01-06 18:19:09
【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>> #include <iostream> #include <cassert> #include <ctime> using namespace std; /** 一句箴言: 为每个元素指定父ID,默认指向自己,当要相连时则将ID指向另外一个元素。看两元素是否能连接的标准为各自指向的根是否一样。(看祖老先人是否一样) 方法:合并指向的时候通过size个数或者rank层数来继续优化指向。rank层数更优 **/ class UnionFind { private: int *parent; int *sz;//sz[i] 表示以i为根的集合中元素的个数 int *rank;//rank[i] 表示以i为根的集合所以表示的层数 int count; public: UnionFind(int count) { this->count = count; parent = new int[count]; sz = new int[count]; rank = new int[count]; for(int i = 0; i < count; i++) { parent[i] = i; sz[i] = 1; rank[i] = 1; } } ~UnionFind() { delete [] parent; delete [

GPU---并行计算利器

笑着哭i 提交于 2020-01-04 05:06:09
大数据集群计算利器之MPI/OpenMP ---以连通域标记算法并行化为例 1 背景 图像连通域标记算法是从一幅栅格图像(通常为二值图像)中,将互相邻接(4邻接或8邻接)的具有非背景值的像素集合提取出来,为不同的连通域填入数字标记,并且统计连通域的数目。通过对栅格图像中进行连通域标记,可用于静态地分析各连通域斑块的分布,或动态地分析这些斑块随时间的集聚或离散,是图像处理非常基础的算法。目前常用的连通域标记算法有1)扫描法(二次扫描法、单向反复扫描法等)、2)线标记法、3)区域增长法。二次扫描法由于简单通用而被广泛使用! 图1 连通域标记示意图 随着所要处理的数据量越来越大,使用传统的串行计算技术的连通域标记算法运行时间过长,难以满足实际应用的效率需求。随着并行计算技术的发展,利用不同的编程模型,许多数据密集型的计算任务可以被同时分配给单机多核或多机多处理器进行并行处理,从而有可能大幅度缩减计算时间。目前在集群计算领域广泛使用MPI来进行并行化,在单机领域广泛使用OpenMP进行化,本文针对基于等价对的二值图像连通域标记算法的进行了并行化设计,利用不同的并行编程模型分别实现了不同的并行算法,并通过实验对利用不同并行编程模型所实现的连通域标记算法进行了性能对比分析。 2 二次扫描串行算法思想    顾名思义,二次扫描串行算法步骤包含两部分。 2.1 第一次扫描 a)标记 b

并查集算法应用

匆匆过客 提交于 2020-01-02 21:44:23
并查集概念 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。 并查集也被称为不相交集数据结构。顾名思义,并查集主要操作是合并与查询,它是把初始不相交的集合经过多次合并操作后合并为一个大集合,然后可以通过查询判断两个元素是否已经在同一个集合中了。 在Python中,并查集可以通过list或者dict等数据结构实现 547. 朋友圈 给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。 并查集的主要方法有:查找/连通/判断是否连通 这道题的代码相对较为简单: class Solution: def findCircleNum(self, M: List[List[int]]) -> int: n = len(M) p = [[i] for i in range(n)] #先生成并查集的节点 ans = n for i in range(n): for j in range(i): if M[i][j] == 1 and p[i] is not p[j]: #如果i和j是好朋友,但p中没有显示则 p[i] += p[j] #在p[i]中加入p[j] for k in p[j]

【算法学习】最小生成树

只愿长相守 提交于 2020-01-02 19:24:16
最小生成树 最小生成树的算法包括Prim算法和Kruskal算法。 在用Kruscal算法的时候会用到并查集。 完整代码可直接翻到最后~ 首先咱们先介绍比较容易理解也是最先会想到的一个算法,Prim算法。 Prim Prim算法的时间复杂度为O(n^2)。 Prim算法的思想就是从图中任意一点选择为始点,然后开始搜索可以从这个点到达其他点的最短距离。其实这个思想和Dijstra算法很相似。 不同之处在于,Dijstra算法计算的是点到点最短距离,Prim算法是把所有点连通起来权值最小。 实质上也就是,Dijstra算法在把某个点加入到集合中时,它的算法仅仅只是把这个点当做一个跳板,内部点与点之间还是存在距离(得到了某个人却没有得到它的心)。而Prim算法则是把这个点合在了一起,也就是在加入某个点之后,就可以把这些点看成一个点,集合内部没有距离(得到某个人并且得到了它的心)。 看例子(从0开始): 用Dijstra算法 首先0,然后0->1, 最后0->2。 用Prim算法 首先0,然后0->1,最后1->2。 所以这两个算法的区别就是在于0->1这个距离在后续中考虑存在与否。更一般的就是被添加到集合中的点与点之间的距离是否还存在。 好的,请看算法核心代码 void Primnew () { int V[N]; //V[] 记录节点的前驱 int dis[N];/

Leetcode200.岛屿数量——并查集

久未见 提交于 2020-01-02 16:12:59
文章目录 引入 并查集 算法细节 Leetcode题解 引入 在leetcode中遇到 这样一道题 : 200.岛屿数量 给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。 示例 1: 输入: 11110 11010 11000 00000 输出: 1 示例 2: 输入: 11000 11000 00100 00011 输出: 3 这样的题,首先想到的就是使用深搜或者宽搜进行查找。 使用深搜宽搜,是这样写代码的:这里类似于泛洪,向四周扩散,已经收到包的弃包。 public void dfs ( char [ ] [ ] grid , int i , int j ) { if ( i < grid . length && j < grid [ 0 ] . length && i > - 1 && j > - 1 ) { if ( grid [ i ] [ j ] == '1' ) { grid [ i ] [ j ] = '0' ; dfs ( grid , i + 1 , j ) ; dfs ( grid , i , j + 1 ) ; dfs ( grid , i - 1 , j ) ; dfs ( grid , i , j - 1 ) ; }

数据结构——并查集

夙愿已清 提交于 2020-01-01 22:22:23
并查集 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。 并查集是一种特殊的树形结构。其主要应用在集合论中。 在集合论中,判断两个点是否在同一集合是一件不简单的事情。在成千上万的数据里,判断两点是否在同一集合是很耗费时间的。因此,引入了并查集的思想。 并查集是一种树形结构,一棵树就是一个集合。判断两个点是否在同一集合中只需判断两个点的根节点是否为一个。如果要将两个集合合并为一个集合,也只需要将这两棵树合并为一棵,省时省力。 在这也会遇到一个问题,在建树的过程中,可能会有某一棵子树度很小导致其深度很大,就像一个线性表一样,这样在寻找根节点是无疑会增加查找时间,因此在进行树的合并时,要比较两棵树的深度,深度小的合并在深度大的树上。如果两棵树深度相同,则随意选一棵树做根树。 在这里,需要两个辅助数组,一个记录,每个节点的双亲节点,一个记录每个节点的深度。 // 初始化双亲数组,每个节点开始默认无双亲,故双亲为-1 // 每个节点默认深度为0 void InitParents ( int parents [ ] , int ranks [ ] ) { for ( int i = 0 ; i < VERTCES ; ++ i ) { parents [ i ] = - 1 ; ranks [ i ] = 0 ; }

File Transfer(并查集)

徘徊边缘 提交于 2019-12-31 21:45:57
题目大意:将多个电脑通过网线连接起来,不断查询2台电脑之间是否连通。 问题来源:中国大学mooc 05-树8 File Transfer (25 分) We have a network of computers and a list of bi-directional connections. Each of these connections allows a file transfer from one computer to another. Is it possible to send a file from any computer on the network to any other? Input Specification: Each input file contains one test case. For each test case, the first line contains N ( 2 ≤ N ≤ 1 0 ​ 4 ​​), the total number of computers in a network. Each computer in the network is then represented by a positive integer between 1 and N. Then in the following lines, the

poj2492(带权并查集)

不打扰是莪最后的温柔 提交于 2019-12-27 06:33:18
题目链接:http://poj.org/problem?id=2492 题意:给出n个人,m条关系,每条关系表示的两个人异性,判断这m条关系是否有误。 思路:带权并查集,类似poj1182,并查集的向量应用。用1表示x,y异性,0表示同性,不访用f(x,y)表示x,y的关系。容易得出f(x,z)=f(x,y)^f(y,z),之后就好做了。输入一组关系t1,t2,用r1,r2表示其祖先,若相等则查询,即f[t1]与f[t2]是否相等; 若不等,则合并,f[r2]=1^f[t1]^f[t2](手动模拟一下)。 AC代码: 1 #include<cstdio> 2 using namespace std; 3 4 int cas,n,m,root[2005],f[2005]; 5 6 int getr(int k){ 7 if(root[k]==k) return k; 8 else{ 9 int tmp=root[k]; 10 root[k]=getr(root[k]); 11 f[k]^=f[tmp]; 12 return root[k]; 13 } 14 } 15 16 int main(){ 17 scanf("%d",&cas); 18 for(int i=1;i<=cas;++i){ 19 bool flag=true; 20 scanf("%d%d",&n,&m); 21