並查集 (Disjoint Set)

1.並查集的實現方法,通常通過一個 set[ i ] 數組實現,其中set[ i ] 表示元素 i 所在的集合。

例如:

i   10
set[i] 2 1 4 2 6 1 6 2 2
一共有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] 1 2 3 2 1 3 4 3 3 4
那麼通過一個查找父節點函數(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;
}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章