首页 > 其他 > 详细

并查集 & 最小生成树详细讲解

时间:2019-10-19 18:13:28      阅读:67      评论:0      收藏:0      [点我收藏+]

并查集 & 最小生成树

并查集 Disjoint Sets

什么是并查集

????并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
????并查集(Disjoint Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。(引自百度百科《并查集》)

简单来说,并查集的主要操作有:

???1- 合并两个不相交的集合
???2- 查询两个元素是否属于同一个集合

老样子,先上引例——

NKOJ 1205 亲戚

????或许你并不知道,你的某个朋友是你的亲戚。他可能是你的曾祖父的外公的女婿的外甥女的表姐的孙子。如果能得到完整的家谱,判断两个人是否亲戚应该是可行的,但如果两个人的最近公共祖先与他们相隔好几代,使得家谱十分庞大,那么检验亲戚关系实非人力所能及。在这种情况下,最好的帮手就是计算机。
  为了将问题简化,你将得到一些亲戚关系的信息,如同Marry和Tom是亲戚,Tom和Ben是亲戚,等等。从这些信息中,你可以推出Marry和Ben 是亲戚。请写一个程序,对于我们的关于亲戚关系的提问,以最快的速度给出答案。
输入格式
输入由两部分组成。
第一部分以N,M开始。N为问题涉及的人的个数(1 ≤ N ≤ 20000)。这些人的
编号为1,2,3,…,N。下面有M行(1 ≤ M ≤ 100000),每行有两个数ai, bi,表示已知ai和bi是亲戚。
第二部分以Q开始。以下Q行有Q个询问(1 ≤ Q ≤ 1 000 000),每行为ci,di,表示询问ci和di是否为亲戚。
输出格式
对于每个询问ci, di,若ci和di为亲戚,则输出yes,否则输出no。
样例输入
10 ?7
2 ?4
5 ?7
1 ?3
8 ?9
1 ?2
5 ?6
2 ?3
3
3 ?4
7 ?10
8 ?9
样例输出
yes
no
yes
传送门http://oi.nks.edu.cn/zh/Problem/Details?id=1205

????从题目中我们可以得到一些提示,它就是要让我们构建一个关系集合出来,再快速查找两个元素是否位于同一集合,这显然就与并查集的效用十分吻合。

合并的过程是怎样的(图示)?

技术分享图片
技术分享图片
技术分享图片
技术分享图片
技术分享图片

并查集的工作原理

技术分享图片
技术分享图片
技术分享图片
基于此算法如此高的时间复杂度,我们采用某种特殊的手段来优化它,这也便是并查集的核心内容——路径压缩
技术分享图片
技术分享图片

下面给出并查集的核心函数:

查询同时路径压缩
int GetFather(int v) {  //查询元素v所在集合的根节点  
    if (Father[v] == v)return v;    //v本身为根  
    else {  
        Father[v] = GetFather(Father[v]);   //只对v到根这条路径上的节点进行路径压缩  
        return Father[v];  
    }  
}  
合并两个集合
void Merge(int x, int y) {  //合并元素x和元素y所在集合  
    int fx, fy;  
    fx = GetFather(x);  //先找出x和y所在集合的根  
    fy = GetFather(y);  //两根不相同,说明x和y位于不同集合  
    if (fx != fy)Father[fx] = fy;   //将fy设为fx的父亲,合并两个集合  
}  

我们回到引例,我们现在可以很轻松地解决此题(伪代码)——

for (i = 1; i <= n; ++ i)Father[i] = i; //初始化  
for (i = 1; i <= m; ++ i) { //读入关系  
    cin >> x >> y;  
    Merge(x, y);  
}  
for (i = 1; i <= q; ++ i) { //回答询问  
    cin >> x >> y;  
    if (GetFather(x) == GetFather(y))  
        cout << "Yes";  
    else cout << "No";  
}  (O(m))

提供几道并查集的简单练习:

NKOJ 3197 岛屿
NKOJ 1046 关押罪犯

###并查集的启发式合并(有缘再补)

最小生成树 Minnimum Spanning Tree(MST)

什么是最小生成树

????一个有 n 个结点连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边。最小生成树可以用kruskal(克鲁斯卡尔)算法或prim(普里姆)算法求出。(引自百度百科《最小生成树》)

简言之,最小生成树就是在一个连通图中生成一棵树,刚好连通所有节点,所含边数(或边权总和最小)。

并查集 & 最小生成树详细讲解

原文:https://www.cnblogs.com/Limbo-To-Heaven/p/11704580.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!