並查集_易懂

並查集主要用於處理不相交集合的合併問題。

並查集:將編號爲1 ~ n的n個的對象劃分爲不相交集合,在每個集合中,選擇其中某個元素代表所在集合,在這個集合中,並查集的操作有初始化、合併、查找。
(1)初始化:
定義數字int s[]是以結點i爲元素的並查集,剛開始每個點屬於獨立的集合,且以元素的值表示它的集s[i]
(2)合併:
把兩個集合合併爲一個集合,以此形成一棵搜索樹
(3)查找:
查找元素是一個遞歸的過程,直到元素的值和它的集相等就可以找到根結點的集。
(4)統計有多少個集
如果s[i] = i,這是一個根結點,是它所在集的代表,統計根結點的數量就是集的數量。
(5)壓縮路徑:
在查詢過程中,查詢元素i所屬的集需要搜索路徑找到根結點,返回的結果是根結點,這條搜索路徑可能很長,如果在返回時順便把i所屬的集改成根結點,就能在O(1)時間內得到結果,此方法成爲壓縮路徑,即將整個搜索路徑上的元素所屬的集全改爲根結點。

//算法核心
int find(int x)
{
	if(x != s[x]) s[x] = find(s[x]);
	return s[x];
}

模板題

合併集合

一共有n個數,編號是1~n,最開始每個數各自在一個集合中。

現在要進行m個操作,操作共有兩種:

“M a b”,將編號爲a和b的兩個數所在的集合合併,如果兩個數已經在同一個集合中,則忽略這個操作;
“Q a b”,詢問編號爲a和b的兩個數是否在同一個集合中;

輸入格式

第一行輸入整數n和m。

接下來m行,每行包含一個操作指令,指令爲“M a b”或“Q a b”中的一種。

輸出格式

對於每個詢問指令”Q a b”,都要輸出一個結果,如果a和b在同一集合內,則輸出“Yes”,否則輸出“No”。

每個結果佔一行。

數據範圍

1≤n,m≤105

輸入樣例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

輸出樣例:

Yes
No
Yes
#include <iostream>
#include<string.h>
using namespace std;
const int N = 100010;
int n, m;
int s[N];
int find(int x)//返回父親結點 + 路徑壓縮 
{
	if(s[x] != x) //s[x]不是x的父結點
		s[x] = find(s[x]);//路徑壓縮,回溯時全部指向一個父結點
	return s[x];//元素值=集值,返回x的父結點
}
int main() 
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n;  i ++ ) s[i] = i;

	while(m -- )
	{
	    char op[2];
	    int a, b;
		scanf("%s%d%d", op, &a, &b);//scanf讀取字符數組用%s會避免讀入空格和回車
	
		if(op[0] == 'M') s[find(a)] = find(b);//合併 讓s[x] = y,就是y的父結點變爲s[x];
		else
		{
			if(find(a) == find(b)) puts("Yes");//這裏就用到了壓縮路徑
			else puts("No");
		}
	}
	return 0;
}

加深理解

舉個簡單例子,一羣人在一起聊天,a認識b,b認識c,c認識d,輸入你想讓對方認識的兩個人,比如a和d,如果他們之間有朋友聯繫,那麼a可以和d打個招呼(“Nice to meet you !”),如果兩個之間相互獨立,輸出"Sorry!I don’t know you!"

#include<iostream>
using namespace std;
const int N = 10010;
int s[N];
int find(int x)
{
	if(s[x] != x) s[x] = find(s[x]);
	
	return s[x];
}
int main() 
{
	int n, m;
	cin >> n >> m;//輸入總人數和多少對認識的人
	for(int i = 1 ; i <= n; i ++ ) s[i] = i;
	
	while( m -- )
	{
		int x, y;
		cin >> x >> y;//比如a認識b
		s[find(x)] = find(y);//合併到一起
	}
	int a, b;
	cin >> a >> b;//輸入你想讓誰和誰打招呼
	if(find(a) == find(b)) cout << "Nice to meet you !";
	else cout << "Sorry!I don't know you!";
}

在這裏插入圖片描述


並查集簡單題

Ubiquitous Religions(無處不在的宗教)

描述

當今世界上有太多不同的宗教,很難一一掌握。您有興趣找出您大學中有多少不同宗教信仰的學生。

您知道您的大學中有n個學生(0 <n <= 50000)。向每個學生詢問他們的宗教信仰是不可行的。此外,許多學生不願意表達自己的信念。避免這些問題的一種方法是,問m(0 <= m <= n(n-1)/ 2)對學生,並詢問他們是否信仰同一宗教(例如,他們可能知道他們是否都參加同一宗教)教會)。從這些數據中,您可能不知道每個人的信仰,但是您可以大致瞭解在校園中可以代表多少種宗教。您可以假設每個學生最多訂閱一種宗教。
輸入值

輸入包含多種情況。每種情況都以指定整數n和m的行開頭。接下來的m行分別由兩個整數i和j組成,指定學生i和j信仰相同的宗教。學生從1到n編號。輸入的結尾由其中n = m = 0的行指定。
輸出量

對於每個測試用例,在一行上打印例號(以1開頭),然後是大學學生所信奉的不同宗教的最大數量。
樣本輸入

10 9 
1 2 
1 3 
1 4 
1 5 
1 6 
1 7 
1 8 
1 9 
1 10 
10 4 4 
2 3 
4 5 
4 8 
5 8 
0 0

樣本輸出

Case 1: 1
Case 2: 7

思路:輸入人數,開始每個人是一個集合,將信仰宗教相同的人合併,然後總集合數減去合併的就是學生信奉的不同宗教的最大數量

#include<iostream>
using namespace std;
const int N = 50050;
int s[N];
int n, m;
int find(int x)
{
	if(s[x] != x) s[x] = find(s[x]); 
	
	return s[x];
}
int main()
{
	int x, y;
	int cnt = 0;	
	while(scanf("%d%d", &n, &m))
	{
		if(n == 0 && m == 0) break; 
		cnt ++ ;
		int ans = 0;
		for(int i = 1; i <= n; i ++ ) s[i] = i;
		
		while(m -- )
		{ 
			scanf("%d%d",&x, &y);
			
			x = find(x);
			y = find(y);
			
			if(x != y)//xy屬於不同集合 
			{
				s[x] = y;//合併 
				ans ++ ;
			}
		} 
		printf("Case %d: %d\n", cnt, n - ans);	
	}	
	return 0;
}

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