1. 程式人生 > >小白專場: File Transfer--集合的簡化表示,按秩歸併與路徑壓縮

小白專場: File Transfer--集合的簡化表示,按秩歸併與路徑壓縮

集合的簡化表示

原始的集合表示:

typedef struct {
ElementType Data;
int Parent;
} SetType;

int Find(SetType S[], Elemtype X)
{	//在陣列S中查詢值為X的元素所屬的集合
	//MaxSize是全域性變數,為陣列S的最大長度
	int i;
	for (i = 0; i < MaxSize && S[i].Data != X; i++);
	if (i >= MaxSize) return -1; //未找到X,返回-1
	for (; S[i].Parent >=
0; i = S[i].Parent); return i;//找到X所屬集合,返回樹根結點在陣列S中的下標 }

集合的簡化表示:

任何有限集合的(N個)元素都可以被一一對映為整數0 ~ N–1

集合中元素在樹結構中的排列沒有順序,所以可以用下標代替代替該元素,每一個下標對應一個元素,形成一種一一對應的關係
在這裡插入圖片描述

typedef int ElementType; /*預設元素可以用非負整數表示*/
typedef int SetName; 	/*預設用根結點的下標作為集合名稱*/
typedef ElementType SetType[MaxSize];
SetName Find(SetType S,
ElementType X) { /* 預設集合元素全部初始化為-1 */ for (; S[X] >= 0; X = S[X]); return X; } void Union(SetType S, SetName Root1, SetName Root2) { /* 這裡預設Root1和Root2是不同集合的根結點*/ S[Root2] = Root1; }

題目:

第一行,表示集合中有幾個元素
C 3 2:檢查3和2是否屬於同一集合
I 3 2:將3和2所在集合合併
S:程式結束,如果只存在一個集合,輸出:The network is connected.如果有n個集合,輸出:There are %d components.
在這裡插入圖片描述

程式框架搭建

int main()
{
	初始化集合;
	do {
		讀入一條指令;
		處理指令;
	} while (沒結束);
	return 0;
}
int main()
{
	SetType S;
	int n;
	char in;
	cin>>n;
	Initialization(S, n);//每一個元素都是一個集合,全部初始化為-1
	do {
		cin>>in;
		switch (in) {
		case 'I': Input_connection(S); break;
		case 'C': Check_connection(S); break;
		case 'S': Check_network(S, n); break;
		}
	} while (in != 'S');
	return 0;
}
//將兩個元素所在集合合併
void Input_connection(SetType S)
{
	ElementType u, v;
	SetName Root1, Root2;
	cin>>u>>v;
	Root1 = Find(S, u - 1);
	Root2 = Find(S, v - 1);
	if (Root1 != Root2)
		Union(S, Root1, Root2);
}
//檢查兩個元素是否屬於一個集合
void Check_connection(SetType S)
{
	ElementType u, v;
	SetName Root1, Root2;
	cin>>u>>v;
	Root1 = Find(S, u - 1);
	Root2 = Find(S, v - 1);
	if (Root1 == Root2)
		printf("yes\n");
	else printf("no\n");
}
//檢視最後一共有幾個集合
void Check_network(SetType S, int n)
{
	int i, counter = 0;
	for (i = 0; i<n; i++)
		if (S[i] < 0) counter++;
	if (counter == 1)
		printf("The network is connected.\n");
	else
		printf("There are %d components.\n", counter);
}

按秩歸併

為什麼要按秩歸併
舉例說明:一直將元素1所在集合合併到其他集合,整個樹的結構就會失衡,樹高度較大,Find()時間複雜度為O(n),對n個結點操作,複雜度為O(n2)
在這裡插入圖片描述

解決方法1:把較矮的樹合併到較高的樹上去

將樹的高度存放在根節的元素中
S[Root]= - 樹高
S[Root]初始化時,仍然初始化為-1

if (S[Root2] < S[Root1])	//S[Root2]高度大,集合1合併到集合2
	S[Root1] = Root2;
else
{
	if (S[Root1] == S[Root2]) S[Root1]--;//集合高度相等,合併後高度增加
	S[Root2] = Root1;
}

最壞情況下,每次合併高度都相等,樹高 = O(log N)

解決方法2:比規模,把小樹貼到大樹上

S[Root]= - 元素個數;

void Union(SetType S, SetName Root1, SetName Root2)
{
	if (S[Root2]<S[Root1])	//S[Root1]所在樹規模較小
	{
		S[Root2] += S[Root1];//樹的規模變為二者元素個數之和
		S[Root1] = Root2;
	}
	else 
	{
		S[Root1] += S[Root2];
		S[Root2] = Root1;
	}
}

兩種方法統稱“按秩歸併”

路徑壓縮

SetName Find(SetType S, ElementType X)
{
	if (S[X] < 0)		//找到集合的根
		return X;
	else
		return S[X] = Find(S, S[X]);
}

在這裡插入圖片描述
這段程式碼幹了三件事

  1. 先找到根;
  2. 把根變成X 的父結點;
  3. 再返回根。

路徑壓縮第一次執行的時間比較長,但是如果頻繁使用查詢命令,第一次將路徑壓縮,大大減小樹的高度,後續查詢速度將大大增加