1. 程式人生 > >並查集的“並優化”(leader合併)和“查優化”(路徑壓縮)

並查集的“並優化”(leader合併)和“查優化”(路徑壓縮)

       在博文http://blog.csdn.net/stpeace/article/details/46506861中, 我們已經詳細地瞭解了並查集, 不過, 那個程式略顯粗糙, 下面我們考慮來優化一下。

       先給出沒有優化的程式碼吧:

// taoge的並查集

#include <iostream>
using namespace std;

#define N 1000
int leader[N + 1] = {0}; // 先搞一個充分大的陣列

// 初始化
void setLeader()
{
	int i = 1;
	for(i = 1; i <= N; i++)
	{
		leader[i] = i; // 初始化時, 將自己初始化為自己的領導
	}
}

// 查詢領導, 看看究竟是誰(實際上, 還可以進行路徑壓縮優化)
int findLeader(int n) 
{
	int r = n;
	while(leader[r] != r)
	{
		r = leader[r]; // 沒找到的話, 一直往上找
	}

	return r;
}

// 將兩個領導帶領的團隊融合, 從此, leaderX和leaderY建立了新的統一戰線, 是一個大家庭團隊了
void uniteSet(int leaderX, int leaderY)
{
	leader[leaderX] = leaderY;  // leader[leaderY] = leaderX;
}

// 輸入陣列, 每一行表示一個集合關係, 比如第一行表示3和4屬於一個集合團隊
int input[] = 
{
	3, 4,
	4, 2,
	7, 6, 
	5, 1,
	3, 9,
	11, 8,
	6, 10,
	9, 13,
	11, 12,
};

// 測試陣列, 測試每行的兩個整數是否屬於同一個大的家庭團隊
int test[] =
{
	3, 2,
	9, 4,
	7, 10,
	6, 7,
	13, 4,
	8, 12,

	6, 9,
	4, 7,
	11, 10,
	1, 2,
	12, 13,
	7, 13,
};


int main()
{
	int numberOfSets = 13; // 總共有13個元素, 即1, 2, 3, 4, ...., 13

	// 初始化領導
	setLeader();

	int i = 0;
	int j = 0;
	int n = sizeof(input) / sizeof(input[0]) / 2;
	for(j = 0; j < n; j++)
	{
		int u = input[i++];
		int v = input[i++];
		
		// 找領導
		u = findLeader(u);
		v = findLeader(v);

		// 領導不相等, 則融合著兩個團隊, 合二為一
		if(u != v)
		{
			uniteSet(u, v);
			numberOfSets--;
		}
	}

	i = 0;
	n = sizeof(test) / sizeof(test[0]) / 2;
	for(j = 0; j < n; j++)
	{
		int u = test[i++];
		int v = test[i++];
		
		// 找領導
		u = findLeader(u);
		v = findLeader(v);

		// 如果領導不相同, 則不屬於一個團隊; 如果兩個領導相同, 則肯定屬於一個團隊
		if(u != v)
		{
			cout << "NO" << endl;
		}
		else
		{
			cout << "YES" << endl;
		}
	}


	// 其實, 經合併後, 最後的集合是4個:
	// {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}
	cout << numberOfSets << endl;

	return 0;
}
       結果為:

YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4


       實際上, 在findLeader的時候, 我們可以進行路徑壓縮, 這是“查優化”的關鍵點。而在並的過程中, 也可以進行“並優化”, 不過, “並優化”的作用不太明顯, 如下:

// taoge的並查集

#include <iostream>
using namespace std;

#define N 1000
int leader[N + 1] = {0}; // 先搞一個充分大的陣列

// 初始化
void setLeader()
{
	int i = 1;
	for(i = 1; i <= N; i++)
	{
		leader[i] = i; // 初始化時, 將自己初始化為自己的領導
	}
}

// 查詢領導, 看看究竟是誰
int findLeader(int n) 
{
	int r = n;
	while(leader[r] != r)
	{
		r = leader[r]; // 沒找到的話, 一直往上找
	}

	// "查優化"的本質是路徑壓縮, 最終使得所有員工的直接上司均為該組的leader
	int i = n;
	int j = 0;
	while(i != r)
	{
		j = leader[i];
		leader[i] = r;
		i = j;
	}

	return r;
}

// 將兩個領導帶領的團隊融合, 從此, leaderX和leaderY建立了新的統一戰線, 是一個大家庭團隊了
void uniteSet(int leaderX, int leaderY)
{
	// 我個人認為:"並優化"的作用不是很大
	if(leaderX < leaderY)
	{
		leader[leaderX] = leaderY;
	}
	else
	{
		leader[leaderY] = leaderX;
	}
}

// 輸入陣列, 每一行表示一個集合關係, 比如第一行表示3和4屬於一個集合團隊
int input[] = 
{
	3, 4,
	4, 2,
	7, 6, 
	5, 1,
	3, 9,
	11, 8,
	6, 10,
	9, 13,
	11, 12,
};

// 測試陣列, 測試每行的兩個整數是否屬於同一個大的家庭團隊
int test[] =
{
	3, 2,
	9, 4,
	7, 10,
	6, 7,
	13, 4,
	8, 12,

	6, 9,
	4, 7,
	11, 10,
	1, 2,
	12, 13,
	7, 13,
};


int main()
{
	int numberOfSets = 13; // 總共有13個元素, 即1, 2, 3, 4, ...., 13

	// 初始化領導
	setLeader();

	int i = 0;
	int j = 0;
	int n = sizeof(input) / sizeof(input[0]) / 2;
	for(j = 0; j < n; j++)
	{
		int u = input[i++];
		int v = input[i++];
		
		// 找領導
		u = findLeader(u);
		v = findLeader(v);

		// 領導不相等, 則融合著兩個團隊, 合二為一
		if(u != v)
		{
			uniteSet(u, v);
			numberOfSets--;
		}
	}

	i = 0;
	n = sizeof(test) / sizeof(test[0]) / 2;
	for(j = 0; j < n; j++)
	{
		int u = test[i++];
		int v = test[i++];
		
		// 找領導
		u = findLeader(u);
		v = findLeader(v);

		// 如果領導不相同, 則不屬於一個團隊; 如果兩個領導相同, 則肯定屬於一個團隊
		if(u != v)
		{
			cout << "NO" << endl;
		}
		else
		{
			cout << "YES" << endl;
		}
	}


	// 其實, 經合併後, 最後的集合是4個:
	// {3, 4, 2, 9, 13}, {7, 6, 10,}, {5, 1}, {11, 8, 12}
	cout << numberOfSets << endl;

	return 0;
}
       結果同樣是:

YES
YES
YES
YES
YES
YES
NO
NO
NO
NO
NO
NO
4

       如果還有理解不清楚的, 請參考我之前的博文http://blog.csdn.net/stpeace/article/details/46506861