并查集 (Disjoint Set)

1.并查集的实现方法,通常通过一个 set[ i ] 数组实现,其中set[ i ] 表示元素 i 所在的集合。

例如:

i  10
set[i]214261622
一共有4个不想交的集合:1,2,4,6;

2.并查集的实现设计

普通情况下可以通过一个简单的for循环在时间复杂度为O(N)实现元素所在集合的合并,但是这种方法在数据比较大的情况下效率偏低。因此,通过为了提高集合的合并那么:

每个集合用一个“有根树”表示,定义数组set[1....n],set[i] = i,则i表示本集合,而且是本集合对应的树根。set[j]=j,j!=i,那么j是i的父节点。

i 2 3 4 5 6  9  10
set[i]1232134334
那么通过一个查找父节点函数(int find(int x)) 和合并函数


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;
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值