1. 程式人生 > >【資料結構】——並查集

【資料結構】——並查集

概念:所謂並查集,就是把一個集合合併再進行查詢。

並查集基本操作:

1、將a、b兩個元素合併在一個集合

2、查詢a、b是否在一個集合

那麼,我們這是就要思考:如何將每個元素合併在一個集合呢?它們之間是否存在著某個標誌?

很顯然,標誌是肯定要存在的。

首先,我們可以將幾個元素組成的集合看成一棵樹,每個元素就是一個節點。那麼我們在這裡就需要藉助一下樹的特性:每個點都有祖先節點。既然如此,我們便可以把標誌轉移到各個元素的祖先節點上:

fa[x]表示元素x的父親節點,若想找其祖先節點,只需要一直遞迴下去便可

那麼首先就把每個元素的父親節點定為自己

那麼並查集有以下幾段優美的程式碼:

查詢公共祖先
int getfa(int k){
    if(k==fa[k]) return k;
    fa[k]=getfa(fa[k]);
    return fa[k];
}
合併
x=getfa(x),y=getfa(y);
if (x!=y) fa[y]=x;
判斷是否在同一個集合
bool panduan(int a,int b){
    return(getfa(a)==getfa(b));
}

//好吧也許我寫得不夠優美

那麼上幾道例題:

一、親戚

  題目描述:有n個人,m個關係,k個查詢。2到k行,a、b,說明a、b為親戚(間接關係也算);k+1到最後,x、y,查詢它們是否為   親戚關係

  題意分析:並查集的一道模板題。將a、b合併在一個集合,就無需考慮間接關係;那麼查詢時只需要判斷x、y是否在同一集合便可

  那麼程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int fa[1000001]={},n,m,p;
int getfa(int k){
    if(k==fa[k]) return k;
    fa[k]=getfa(fa[k]);
    return fa[k];
}//查詢祖先節點
int main(){
    cin>>n>>m>>p;
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int i=1;i<=m;i++){
	    int x,y;
	    cin>>x>>y;
	    x=getfa(x),y=getfa(y);
	    if (x!=y) fa[y]=x;//如果不在同一集合,便合併,使他們成為親戚
	}
	for (int i=1;i<=p;i++){
	    int x,y;
	    cin>>x>>y;
	    int xx=getfa(x),yy=getfa(y);
	    if (xx==yy)cout<<"Yes"<<endl;//如果在同一個集合,說明是親戚
	    else cout<<"No"<<endl;//不然擇不是
	}
	return 0;
}

二、犯罪團伙

   題意描述:有a、b兩個人,他們要麼是朋友,要麼是敵人。 而且有一點是肯定的: a的朋友的朋友是a的朋友; a的敵人的敵人也是a的朋友。 兩個強盜是同一夥的當且僅當他們是朋友。現在小明同學想知道有多少個犯罪同夥

  題目分析:這道題我開始做的時候也是毫無思路,但仔細讀了幾遍題目後,就知道了一個重點——敵人的敵人就是朋友!那麼這樣我們就可以用一個數組f[x]儲存x的敵人。如此,我們就可以把敵人的敵人合併在一個集合中,即是朋友,在一個團伙

  合併完後,我們便可以查詢有幾個團伙

  那麼程式碼如下:

#include<bits/stdc++.h>
using namespace std;
int a[1000001]={},fa[1000001]={},n,m;
int getfa(int k){
    if (k==fa[k]) return k;
    fa[k]=getfa(fa[k]);
    return fa[k];
}
int main(){
    cin>>n>>m;
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int i=1;i<=m;i++){
	    int x,y;
	    char ch;
	    cin>>ch;
		cin>>x>>y;
		if (ch=='F'){
		    int xx,yy;
		    xx=getfa(x);yy=getfa(y);
		    if (xx!=yy) fa[yy]=xx;//如果是朋友,直接合並
		}
		else{
			int xx,yy;
		    if (a[x]==0)a[x]=y;
		    if (a[y]==0)a[y]=x;//敵人的敵人是朋友,記錄敵人的敵人
		    xx=getfa(x);yy=getfa(a[y]);//把自己的祖先和敵人的敵人的祖先找到
		    if (xx!=yy) fa[yy]=xx;//合併
		    xx=getfa(y);yy=getfa(a[x]);//同樣
		    if (xx!=yy) fa[yy]=xx;//合併
		}
	}
	bool f[100001]={};
    int s=0;
	for (int i=1;i<=n;i++){
	    int x=getfa(i);
	    if (!f[x]) s++,f[x]=1;//如果祖先沒出現過,說明是新的一個集合,就加一
	}
	cout<<s;//輸出
	return 0;
}