1.并查集的实现方法,通常通过一个 set[ i ] 数组实现,其中set[ i ] 表示元素 i 所在的集合。
例如:
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
set[i] | 1 | 2 | 1 | 4 | 2 | 6 | 1 | 6 | 2 | 2 |
2.并查集的实现设计
普通情况下可以通过一个简单的for循环在时间复杂度为O(N)实现元素所在集合的合并,但是这种方法在数据比较大的情况下效率偏低。因此,通过为了提高集合的合并那么:
每个集合用一个“有根树”表示,定义数组set[1....n],set[i] = i,则i表示本集合,而且是本集合对应的树根。set[j]=j,j!=i,那么j是i的父节点。
i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
set[i] | 1 | 2 | 3 | 2 | 1 | 3 | 4 | 3 | 3 | 4 |
int find(int x)
{
int r = x;
while( set[r] != r )
r = set[r];
return r;
}//时间复杂度最坏情况下O(N),一般情况下是O(lgN)
void merge(int a,int b)
{
if( a < b )
set[b] = a;
else
set[a] = b;
}//时间复杂度是O(1);
为了进一步优化,那么需要减小建树的深度,因此需要对merge函数进行优化,从而是find函数的最坏情况复杂度为O(lgN),优化如下,
void merge3(a,b)
{
if (height(a) == height(b))
{
height(a) = height(a) + 1;
set[b] = a;
}
else if (height(a) < height(b))
set[a] = b;
else
set[b] = a;
}//其中height可以通过使用结构体增设变量。
int find2(x)//带路径压缩的查找算法。
{
int r = x;
while (set[r] <> r) //循环结束,则找到根节点
r = set[r];
int i = x;
while (i != r) //本循环修改查找路径中所有节点
{
int j = set[i];
set[i] = r;
i = j;
}
}
以HDOJ1232 题目为例,具体的实现细节代码如下:
//Time:31MS
//Mem :212K
#include <stdio.h>
struct inode
{
int parent;
int height;
};
inode node[1002];
int ans;
int find(int x)
{
int r = x;
while(node[r].parent != r)
r = node[r].parent;
int i=x,j;
while(i != r)
{
j = node[i].parent;
node[i].parent = r;
i = j;
}
return r;
}
void merge(int x,int y)
{
int fx,fy;
fx = find(x);
fy = find(y);
if(fx == fy)
return;
else
{
if(node[fx].height == node[fy].height)
{
node[fx].parent = fy;
node[fy].height++;
}
else if(node[fx].height > node[fy].height)
node[fy].parent = fx;
else
node[fx].parent = fy;
}
}
int main()
{
int n,m,i,x,y;
while(scanf("%d",&n),n)
{
ans = 0;
for(i=1; i<=n; i++)
{
node[i].parent = i;
node[i].height = 1;
}
for(scanf("%d",&m);m>0; m--)
{
scanf("%d %d",&x,&y);
merge(x,y);
}
for(int i=1;i<=n;i++)
if(node[i].parent == i)
ans++;
printf("%d\n",ans-1);
}
return 0;
}
//莫名其妙带查找合并优化的代码AC的时间居然是一样 - -!
应用:HDOJ 1272(小希的迷宫)http://acm.hdu.edu.cn/showproblem.php?pid=1272
解题思想:可以通过判断输入的迷宫,是否是一个连通图。
if(输入的节点在一个结合内){
if(节点数目 == 边的数目-1)//是一棵树,而不是图,另外得特别注意,都为0的情况,
yes
else
no
}
else
no
实现如下:
//Time:62MS
//Mem :1080K
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
#define MAX 100001
int visited[MAX];
int set[MAX];
int find(int x)
{
int r = x;
while(set[r]!=r)
r = set[r];
set[x] = r;
return r;
}
void merge(int a, int b)
{
int fa = find(a);
int fb = find(b);
set[fa] = fb;
}
int main()
{
int n,m;
while(true)
{
int node_num = 0;//初始化节点数目
int line_num = 0;//边的数目
int set_num = 0;//节点的结合数目
for(int i=1;i<=100000;i++)
set[i] = i;
memset(visited, 0, sizeof(visited));
while(scanf("%d %d",&n,&m)!=EOF)
{
if((n==0 && m==0) || (n==-1&&m==-1))
break;
line_num++;
visited[n] = 1;
visited[m] = 1;
merge(n,m);
}
if(n==-1 && m==-1)
break;
for(int i=1;i<=100000;i++)
{
if(visited[i]==1 && set[i]==i)
set_num++;
if(visited[i] == 1)
node_num++;
}
if(node_num == 0)//特别注意0节点的情况,是yes
printf("Yes\n");
else if(set_num == 1 && line_num==node_num-1)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}