并查集算法讲解+例题
2016-03-06 01:31
375 查看
定义:并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint
Sets)的合并及查询问题。常常在使用中以森林来表示。
考虑这样一个问题:
小明马上要过生日,要邀请N个好友,而他的好友都只愿意和认识的人坐,给出朋友之间的认识关系,假设所有认识的人坐一桌,问最少需要多少桌子。
规定:若A认识B,B认识C,则A认识C,则ABC全部认识,可以坐一桌;
例如:小明的好友为 A B C D E 五人,关系式 A-B B-C D-E。则此时需要两个桌子:ABC一桌,DE一桌。
输入N表示好友人数,M表示关系数,接下来M行显示所有关系。
样例输入:
问题分析:
如果对于所有的朋友编号1~N
不难想到,用集合来表示朋友之间的关系,就样例而言,最初没有给出任何关系的时候,9个人互相都不认识,于是有9个集合:
{1},{2},{3},{4},{5},{6},{7},{8},{9}
当依次输入关系之后,就可以依次合并集合:
输入:2 4 :{1},{2,4},{3},{5},{6},{7},{8},{9}
输入:5 7:{1},{2,4},{3},{5,7},{6},{8},{9}
输入:1 3:{1,3},{2,4},{5,7},{6},{8},{9}
输入:8 9:{1,3},{2,4},{5,7},{6},{8,9}
输入:1 2:{1,2,3,4},{5,7},{6},{8,9}
输入:5 6:{1,2,3,4},{5,6,7},{8,9}
输入:2 3:{1,2,3,4},{5,6,7},{8,9}
最后有3个集合,也就是所有的朋友分为3群,也就是说,需要3张桌子,最后的答案是3.
很简单?是的,让人来合并集合确实很简单,但是如何让计算机去合并集合呢?
你当然可以暴力的合并,但是超时那是妥妥的,于是伟大的并查集算法诞生了!
没错,这就是典型的并查集问题,对此类问题我们先画一下上面的关系:
![](http://img.blog.csdn.net/20160306022436316?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](http://img.blog.csdn.net/20160306022523735?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](http://img.blog.csdn.net/20160306022541473?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
只是将之前的集合关系画成图,还画的那么丑,并没有什么用啊?
我们开这样一个数组father
;
其中,father[i] = j表示,i这个点的父结点是j
对应上面第3张图,也就是:
father[3] = 1,father[1] = 4,father[4] = 2,father[2] = 2;//想想这里为什么让father[2] = 2
father[7] = 5,father[5] = 6,father[6] = 6;
father[9] = 8,father[8] = 8;
仔细观察上面的关系,例如:father[ father[1] ] = 2;体现了1的父亲的父亲是2。然后你还会发现,上面3排的最后一个关系可以写成father[i] = i。这样的形式告诉我们,i已经是根节点了,没有必要再继续往上面找了。
这个图你懂了,但是这又有什么用呢?
不,你突然发现,用集合表示出来的关系已经可以体现出来了!
{1,2,3,4},{5,6,7},{8,9},这3个集合,以及第三张图......
似的,你发现,在图三中被一根线连起来的一坨就是一个集合,而那一坨的所有元素共有一个根节点!
于是,问题迎刃而解,你只需要把“线”连起来,然后最后找一找根节点的个数就好,然后你又看出father[i] = i就是根节点所具有的性质。
于是我们便可以开始并查集算法了:
首先,不管是集合还是图,最初的初始情况是谁也不认识谁,每个点都是单独的个体,你也可以理解成每个点都是根节点,于是我们这样初始化:
初始化代码如下:
找根节点:
这个地方有人可能会说:你这个循环不就相当于一个if 吗?x!=father[x]就让x=father[x]嘛,然后就可以跳出循环了嘛!
似乎很有道理,但是你就刚刚的“father[3] = 1,father[1] = 4,father[4] = 2,father[2] = 2”来模拟一下,
假设传入的x是3,x!=father[x],于是x = father[x],此时的 x = 1,再带入while循环的判断框,咦,原来这个时候的x=1,father[x] = 4依旧不等,然后你明白了其中的奥妙。
(可能你是一眼看出,但介于本渣当初学并查集的时候看了好久才明白,这里还是提一下,希望对于新手的理解更有帮助)
合并:
基本上掌握上面3个模板,并查集就算入门了,那么直接上一道模板题练练手
题目传送门:点击打开链接
上AC代码:
做完这道简单题,于是你开是在并查集的路上越走越远,然后有一天,你发现你的并查集超时了!!!
于是,你开始知道一种名为“路径压缩”的优化方式:
什么叫“路径压缩”呢?其实就是一张图!
假设我最初给出的关系画成图是这样:
![](http://img.blog.csdn.net/20160306030639024?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在这个图的基础上,你可能需要多次去找4的根节点,如果每次找都要通过4-3-2-1的路线去找就太傻了,于是有了下面这张图:
![](http://img.blog.csdn.net/20160306031122796?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在每一次找到一个点的根节点之后,直接把这个点连到根节点上去,这样以后再找就会省下很多时间!
知道这个原理代码再修改起来也很简单,只需要在找爸爸的地方记录一下就好了
然后同样是之前的那个题目可以试着用路径压缩做的试试,对比一下运行时间。
Sets)的合并及查询问题。常常在使用中以森林来表示。
考虑这样一个问题:
小明马上要过生日,要邀请N个好友,而他的好友都只愿意和认识的人坐,给出朋友之间的认识关系,假设所有认识的人坐一桌,问最少需要多少桌子。
规定:若A认识B,B认识C,则A认识C,则ABC全部认识,可以坐一桌;
例如:小明的好友为 A B C D E 五人,关系式 A-B B-C D-E。则此时需要两个桌子:ABC一桌,DE一桌。
输入N表示好友人数,M表示关系数,接下来M行显示所有关系。
样例输入:
9 7 2 4 5 7 1 3 8 9 1 2 5 6 2 3
问题分析:
如果对于所有的朋友编号1~N
不难想到,用集合来表示朋友之间的关系,就样例而言,最初没有给出任何关系的时候,9个人互相都不认识,于是有9个集合:
{1},{2},{3},{4},{5},{6},{7},{8},{9}
当依次输入关系之后,就可以依次合并集合:
输入:2 4 :{1},{2,4},{3},{5},{6},{7},{8},{9}
输入:5 7:{1},{2,4},{3},{5,7},{6},{8},{9}
输入:1 3:{1,3},{2,4},{5,7},{6},{8},{9}
输入:8 9:{1,3},{2,4},{5,7},{6},{8,9}
输入:1 2:{1,2,3,4},{5,7},{6},{8,9}
输入:5 6:{1,2,3,4},{5,6,7},{8,9}
输入:2 3:{1,2,3,4},{5,6,7},{8,9}
最后有3个集合,也就是所有的朋友分为3群,也就是说,需要3张桌子,最后的答案是3.
很简单?是的,让人来合并集合确实很简单,但是如何让计算机去合并集合呢?
你当然可以暴力的合并,但是超时那是妥妥的,于是伟大的并查集算法诞生了!
没错,这就是典型的并查集问题,对此类问题我们先画一下上面的关系:
只是将之前的集合关系画成图,还画的那么丑,并没有什么用啊?
我们开这样一个数组father
;
其中,father[i] = j表示,i这个点的父结点是j
对应上面第3张图,也就是:
father[3] = 1,father[1] = 4,father[4] = 2,father[2] = 2;//想想这里为什么让father[2] = 2
father[7] = 5,father[5] = 6,father[6] = 6;
father[9] = 8,father[8] = 8;
仔细观察上面的关系,例如:father[ father[1] ] = 2;体现了1的父亲的父亲是2。然后你还会发现,上面3排的最后一个关系可以写成father[i] = i。这样的形式告诉我们,i已经是根节点了,没有必要再继续往上面找了。
这个图你懂了,但是这又有什么用呢?
不,你突然发现,用集合表示出来的关系已经可以体现出来了!
{1,2,3,4},{5,6,7},{8,9},这3个集合,以及第三张图......
似的,你发现,在图三中被一根线连起来的一坨就是一个集合,而那一坨的所有元素共有一个根节点!
于是,问题迎刃而解,你只需要把“线”连起来,然后最后找一找根节点的个数就好,然后你又看出father[i] = i就是根节点所具有的性质。
于是我们便可以开始并查集算法了:
首先,不管是集合还是图,最初的初始情况是谁也不认识谁,每个点都是单独的个体,你也可以理解成每个点都是根节点,于是我们这样初始化:
初始化代码如下:
void init() { for (int i = 0;i <= n;i++) { father[i] = i; } }
找根节点:
int getfather(int x) { while (x != father[x]) { x = father[x]; } return x; }
这个地方有人可能会说:你这个循环不就相当于一个if 吗?x!=father[x]就让x=father[x]嘛,然后就可以跳出循环了嘛!
似乎很有道理,但是你就刚刚的“father[3] = 1,father[1] = 4,father[4] = 2,father[2] = 2”来模拟一下,
假设传入的x是3,x!=father[x],于是x = father[x],此时的 x = 1,再带入while循环的判断框,咦,原来这个时候的x=1,father[x] = 4依旧不等,然后你明白了其中的奥妙。
(可能你是一眼看出,但介于本渣当初学并查集的时候看了好久才明白,这里还是提一下,希望对于新手的理解更有帮助)
合并:
void Merge(int a,int b) { int fa = getfather(a); int fb = getfather(b); if (fa != fb) { father[fa] = fb; } }
基本上掌握上面3个模板,并查集就算入门了,那么直接上一道模板题练练手
题目传送门:点击打开链接
上AC代码:
//Must so
#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
#define sqrt(n) sqrt((double)n)
#define pow(a,b) pow((double)a,(int)b)
#define inf 1<<29
#define NN 1006
using namespace std;
const double PI = acos(-1.0);
typedef long long LL;
int n,m;
int father[NN];
void init()
{
for (int i = 0; i <= n; i++)
{
father[i] = i;
}
}
int getfather(int x) { while (x != father[x]) { x = father[x]; } return x; }
void Merge(int a,int b) { int fa = getfather(a); int fb = getfather(b); if (fa != fb) { father[fa] = fb; } }
int main()
{
int T;
cin>>T;
while (T--)
{
cin>>n>>m;
init();
for (int i = 0,a,b; i < m; i++)
{
scanf("%d%d",&a,&b);
Merge(a,b);
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (father[i] == i) ans ++;
}
cout<<ans<<endl;
}
return 0;
}
做完这道简单题,于是你开是在并查集的路上越走越远,然后有一天,你发现你的并查集超时了!!!
于是,你开始知道一种名为“路径压缩”的优化方式:
什么叫“路径压缩”呢?其实就是一张图!
假设我最初给出的关系画成图是这样:
在这个图的基础上,你可能需要多次去找4的根节点,如果每次找都要通过4-3-2-1的路线去找就太傻了,于是有了下面这张图:
在每一次找到一个点的根节点之后,直接把这个点连到根节点上去,这样以后再找就会省下很多时间!
知道这个原理代码再修改起来也很简单,只需要在找爸爸的地方记录一下就好了
int getfather(int x) { int xx = x;//保存这个需要找爸爸的点 while (x != father[x]) { x = father[x]; } father[xx] = x;//直接将这个点连到根节点上去 return father[xx]; }
然后同样是之前的那个题目可以试着用路径压缩做的试试,对比一下运行时间。
相关文章推荐
- chrome gif录制工具 Animated Gif Capture
- opencv2.0 DataType 实现
- IOS 日期选择器 UIDatePicker的使用
- Linux系统运维/Bash/5-5-数据流重定向
- jQuery.validate.js之二【整理】
- 移动端H5的一些基本知识点总结
- POJ 1753 Flip Game (枚举 + 位运算)
- linux 内核 第二周 操作系统是如何工作的
- Formal Languages and Compilers(1)-Left and right linear grammars
- CSS学习(七)-边框阴影效果(下)
- android引导页viewpager实现
- solr查询语法
- Android 自定义View总结
- 通过使用DOSBOX运行MASM
- iOS开发--block
- 用gem安装cocoapods工具包
- linux内核分析 第二周 操作系统是如何工作的
- 质数与合数系列——分解质因数
- 高精度阶乘
- 单选按钮RadioButton及同时选择两个关联button的实现