【精講】圖論算法——並查集入門


某個家族人員過於龐大,

要判斷兩個人是否是親戚很不容易。

現在給出某個親戚關系圖,

求任意給出的兩個人是否具有親戚關系。

這是親戚的題面


我們先不要管這道題的輸入輸出,

我們假設他給出的不是兩個人的親戚關系,

而是告訴你a是b的兒子。

那我這個時候就出現了二條

基本的法則:

1.如果a是b的兒子,那么b就不是a的兒子。

2.一個人可以有多個兒子,但只能有一個父親。


這個時候我們再來思考,

什么情況下兩人構成親戚關系呢?

他們可能是兄弟關系,也就是有共同的父親,

有可能是父子關系,也就是一方是另一方的父親,

有可能是叔侄關系……

關系有很多,但其實只有一條——

親戚的親戚就是親戚,

跟自己親戚不是親戚的就不是親戚。


根據這條,我們可以將一個家族譜簡單處理。

由此發現,無論如何,

每個小家庭都構成一個樹,

而整個大家庭就是一片森林。

因此親戚其實就是

判斷他是否在同一個樹。


只需要記錄下來他們的父親,

不斷遞歸,來判斷樹根是否一致,

樹根就是自己是自己的父親。

代碼如下:

int find(int x)
{
	if(x!=fa[x])//如果不是樹根
	{
		return find(fa[x]);//返回父親的父親
	}
	return fa[x];//不然返回樹根
}


這樣子看起來很完美,

事實上問題也很明顯,

那就是當數據足夠大的時候。

遞歸會顯得很吃力。


其實我們求的是樹根,

但是我們卻每一次都要不斷的遞歸,

如果父親數組直接記錄樹根,那就可以節省很多時間。

我們每一次去找父親的父親時,

返回來的是樹根,

這個時候我們把父親設為這個樹根,

等到下一次再搜的時候可以直接返回樹根。


當然這個方法也有問題——大逆不道

前一秒你的父親還是你的父親,

后一秒他就跟你是兄弟了。

當然,你父親的父親也成了你的兄弟……

但我們才管他是不是大孝子,我們能AC就行,


由此打出優化

int find(int x)
{
	if(x!=fa[x])//如果不是樹根
	{
		fa[x]=find(fa[x]);//把父親設為父親的父親
	}
	return fa[x];//返回樹根
}

現在我們重新看回題面,

打出這個題目的代碼。

#include<bits/stdc++.h>
using namespace std;
int fa[100001];
int find(int x)
{
	int son=x;
	if(x!=fa[x])
	{
		fa[x]=find(fa[x]);
	}
	return fa[x];
}
void join(int a,int b)//合並的代碼,僅供參考。
{
	int x=find(a); 
	int y=find(b);
	if(x!=y)fa[x]=fa[y];//注意是設為別人的父親,這是一個優化。
	return;
}
bool pd(int a,int b)
{
	int x=find(a); 
	int y=find(b);
	if(x!=y)return false;
	else return true;
}
int main()
{
	int n,m,k;
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1; i<=n; i++)fa[i]=i;
	for(int i=1; i<=m; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		join(x,y);
	}
	scanf("",);
	for(int i=1; i<=k; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		if(pd(x,y)) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

這個算法就是並查集算法,

並代表合並,

查代表查詢,

集代表集合。

也就是可以合並和查詢的一個集合。

並查集雖然是一種圖論算法,但其實只要學了遞歸就可以學。

那么他為什么是一種圖論算法呢?

因為它可以判斷回路,

同時還存在帶權並查集這種玩法,

最小生成樹經典算法——

Kruskal(克魯斯卡爾)算法就是用並查集實現判斷回路的。

如果評論區有人讓我講的話,

我以后可能會去把這個克魯斯卡爾算法講一下。


那么今天就講到這里,如有不對請指正!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM