一.并查集的概念
并查集是一种算法可以用来判断相互关联(同属一个集合)的元素属于几个集合,也可以用来判断图结构中的两点是否是联通的。并查集的设计思路是这样的:
在程序执行过程中任意元素一定输于以下三种状态
1.即f[i]=i,在该种状态下的元素可能是未被合并(初始状态),也可能是经过合并但是选择的父节点就是这个节点
2.已有父节点并且就是当前状态下真正的父节点(其实是最正常的状态,第二种状态也包含第一种情况)。
3.已有父节点但并非当前状态下真正的父节点,可能是以前的父节点,由merge函数可以看出,当合并两个节点时只是合并两个节点的父节点,当这个节点的父节点不是本身的时候这个节点的父节点就不会被更改成新的父节点,这种状态在统计集合数量的时候是危险的,因为回使得集合数量增多,但其实并不用担心这个问题,仔细考察getf函数(查找父节点)的时候就会发现f[a]=getf(f[a])这样一段代码,原来getf函数不仅返回了每个节点的父亲,还顺便修改了每个节点的f[]值使它和他父亲的f[]值一致,所以每当该节点或这个节点的子节点被merge再次合并地时候,这个节点的f[]就会被修改成正确值,但是问题就来了,万一该节点没有被再次合并怎么办?保险一点的操作应该是在merge完所有的节点之后再getf以下所有的节点,这样所有的节点就正确了。
二.并查集的应用
1.用来合并集合元素,并确定结合数量,查寻元素属于哪个集合。
2.在图结构里(图里的点便是元素),确定两点是否处于联通状态,应用举例:
(1)Kruskal法最小生成树
思路:将所有的边依照长度大小排序,依次从小到大进行行选泽,每次选中一边,就将边两端的点用并查集合并,如果选择的边经并查集查询已经联通,那么跳过这条边
问题
给定一个有权值的图,找出联通图内所有节点的最小路径。
数据
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
代码
//
// main.cpp
// 图的最小生成树之kruskal算法
//
// Created by 张嘉韬 on 16/3/18.
// Copyright ? 2016年 张嘉韬. All rights reserved.
//
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int f[50];
struct line
{
int u;
int v;
int w;
}lines[50];
int cmp(const void *a,const void *b)
{
return (*(line *)a).w>(*(line *)b).w?1:-1;
}
void init(int n)
{
for(int i=1;i<=n;i++) f[i]=i;
}
int getf(int a)
{
if(f[a]==a) return a;
else
{
f[a]=getf(f[a]);
return f[a];
}
}
int merge(int a,int b)
{
int t1,t2;
t1=getf(a);
t2=getf(b);
if(t1!=t2)
{
f[t2]=t1;
return 1;
}
else return 0;
}
int main(int argc, const char * argv[]) {
freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
int n,m,sum;
sum=0;
cin>>n>>m;
init(n);
for(int i=0;i<=m-1;i++)
{
cin>>lines[i].u>>lines[i].v>>lines[i].w;
}
qsort(lines,m,sizeof(lines[0]),cmp);
for(int i=0;i<=m-1;i++) cout<<lines[i].w<<" ";
cout<<endl;
for(int i=0;i<=m-1;i++)
{
if(merge(lines[i].u,lines[i].v)==1)
{
sum+=lines[i].w;
cout<<i+1<<" ";
}
}
cout<<endl;
cout<<sum<<endl;
return 0;
}
三.并查集的模板
1.问题
给定m组n个元素之间的关系,问这些元素属于个集合
2.输入
#include <iostream>
#include <cstring>
using namespace std;
int f[50];
void init(int n)
{
for(int i=1;i<=n;i++) f[i]=i;
}
int getf(int a)
{
if(f[a]==a) return a;
else
{
f[a]=getf(f[a]);
return f[a];
}
}
void merge(int a,int b)
{
int t1,t2;
t1=getf(a);
t2=getf(b);
if(t1!=t2)
{
f[t2]=t1;
}
}
void print(int n)
{
for(int i=1;i<=n;i++) cout<<"("<<i<<")"<<f[i]<<" ";
cout<<endl;
}
int main(int argc, const char * argv[]) {
freopen("/Users/zhangjiatao/Desktop/input.txt","r",stdin);
int n,m,a,b,sum;
sum=0;
cin>>n>>m;
init(n);
for(int i=1;i<=m;i++)
{
cin>>a>>b;
merge(a,b);
print(n);
}
for(int i=1;i<=n;i++)
{
f[i]=getf(i);
}
for(int i=1;i<=n;i++)
if(f[i]==i) sum++;
cout<<sum<<endl;
return 0;
}
10 8
1 2
3 4
5 2
4 6
2 6
8 7
9 7
1 6
2 4