1. 程式人生 > >並查集--演算法,優化,變種

並查集--演算法,優化,變種

一、定義
並查集是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。
基礎的並查集能實現以下三個操作:1.建立集合;2.查詢某個元素是否在一給定集合內(或查詢一個元素所在的集合); 3.合併兩個集合.“並”“查”“集”三字由此而來。
並查集能解決的問題一般可以轉化為這樣的形式:初始時n個元素分屬不同的n個集合,通過不斷的給出元素間的聯絡,要求實時的統計元素間的關係(即是否存在直接或間接的聯絡)。
並查集本身不具有結構,可以用陣列、連結串列以及樹等實現。最常用的是陣列實現。

二、實現
陣列實現:
建立標記陣列father,用father[i]表示元素i所屬集合的標記。


圖示

1.建立集合
開始時每個元素各自獨立,可以把每個元素所屬集合標記為其自身序號。

void make()
{
	int i;
	for(i=1;i<=n;i++)
    		father[i]=i;
}

2.合併集合&查詢元素所屬集合
find函式返回的是元素所屬集合的根結點(別忘了並查集是樹型的資料結構)。方法就是迴圈或遞迴,尋找當前結點的父結點,父結點的父結點,父結點的父結點的父結點......直到找到一個結點的父結點是它自己,那麼它就是根結點。

int find(int x)//非遞迴寫法
{
	while(father[x]!=x) x=father[x];
	return x;
}
int find(int x)//遞迴寫法
{
	if(father[x])!=x)	return find(father[x]);
	return x;
}

因此,比較兩個元素x,y是否是同一集合的方法就是比較find(x)是否等於find(y)。

bool judge(int x,int y)
{
    x=find(x);
    y=find(y);
    if(x==y)
        return true;
    else
        return false;
}

合併集合的方法就是將其中一個點所在集合的根結點的父結點設定為另一個點所在集合的根結點。

void union1(int x,int y)//union是關鍵字,不能用,函式名可以隨便換一個方便的
{
    x=find(x);
    y=find(y);
    father[y]=x;
}

優化:
1.路徑壓縮

前面的做法就是將元素的父親結點指來指去地指,當這棵樹是鏈的時候,可見判斷兩個元素是否屬於同一集合需要O(n)的時間。
舉個例子:在前面方法的第5步後,如果要查詢第3、5元素所在集合的根結點,那麼每次都需要查詢father[3](father[5])、father[2](father[4])、father[1]的值。
於是,路徑壓縮產生了作用。
路徑壓縮就是在找完根結點之後,在遞歸回來的時候順便把路徑上元素的父親結點都設為根結點。

int find(int x)//遞迴寫法
{
    if(father[x]!=x)
        father[x]=find(father[x]);
    return father[x];
}
int find(int x)//非遞迴寫法,不太好記但是更快,列幾組資料試一下也不難理解
{
	int r=x,q;
	while(r!=father[r])
		r=father[r];
	while(x!=r)
	{
		q=father[x];
		father[x]=r;
		x=q;
	}
	return r;
}
(這個優化平時都可以用,但是對於某些題會造成麻煩,例如加權並查集,這樣的題需要特殊處理) 2.按秩合併(啟發式合併)
在合併兩個集合(就是兩棵樹)的時候,如果待合併的樹的深度不相同,那麼就有兩種選擇:一種是以深度較小的樹的根結點為新的根結點,另一種是以深度較大的樹的根結點為新的根結點。而事實上,選擇以深度較大的樹的根結點為新的根結點較好,因為這樣的話新生成的樹深度會更小,可以防止樹的退化(退化指越來越接近連結串列,即深度大而分支少),使資源利用更合理。而合併時這樣選擇,就叫做“按秩合併”。
按秩合併的基本思想是將深度較小的樹指到深度較大的樹的根上。
按秩合併需要新開一個數組depth來記錄深度。depth[x]是(("以x為根結點的樹"的某個葉結點到x的最長路徑上)邊的數目)的一個最大值。(即以x為根結點的樹的樹高)
(這個優化比較麻煩,簡單題一般不用)
void make()
{
	int i;
	for(i=1;i<=n;i++)
	{
		father[i]=i;
		depth[i]=0;//如果初值為0則可以省略 
	}
}
void union1(int x,int y)
{
	int fx=find(x),fy=find(y);
	if(depth[fx]>depth[fy])
		father[fy]=fx;
	else
	{
		father[fx]=fy;
		if(depth[fx]==depth[fy])
			depth[fy]++;
	}
}
三、並查集的經典應用
①求無向圖最小生成樹的Kruskal演算法
Kruskal將一個連通塊當做一個集合(連通塊指無向圖中相互連通的一些點)。對於一張有n個點的無向圖,首先將所有的邊按從小到大排序,並認為每一個點都是孤立的,分屬於n個獨立的集合。然後按順序列舉每條邊。如果這條邊連線兩個不同的集合,那麼就將這條邊加入最小生成樹,這兩個不同的集合就合併成了一個;如果這條邊連線的兩個點屬於同一集合,就跳過。直到選了n-1條邊為止。具體參見Kruscal演算法 四、並查集的變種
①帶權並查集(加權並查集)
有的時候,不僅需要像普通並查集一樣記錄一些元素之間有無關係,還需要記錄它們之間有怎樣的關係,這時候就需要引入加權並查集。
通常情況下,用一個數組r來記錄這些關係,r[i]表示元素i與父結點的關係。至於是什麼關係,還要根據具體要求來看。
在find(x)函式中進行路徑壓縮的同時,許多結點的父結點會改變,這時就需要根據實際情況調整權值以保證其正確性。
在union1(x,y)函式中,(不妨設將y集合併入x集合)由於y的父結點的改變,需要調整y對應的權值,但不需要調整y的子結點對應的權值,因為子結點權值會在find(子結點)時得到調整。
典型例題:
1.銀河英雄傳說

一道裸的加權並查集的題
2.食物鏈
一道變式題 ②種類並查集--建立補集法 有的時候,元素之間有一些關係,但是關係可以分為幾類,要求記錄它們之間有怎樣的關係,這時可以用建立補集法。 如果有n個元素從a[1]到a[n],它們之間的關係有關係1和關係2兩種,那麼可以建立陣列fa,fa[i]表示與i關係為關係1的元素的集合的序號,fa[i+n]表示與i關係為關係2的元素的集合的序號。當讀入i和j為關係1時,則合併i、j所在集合還有i+n、j+n所在集合;當讀入i和j為關係2時,合併i、j+n所在集合還有i+n和j所在集合。 典型例題: 1.團隊
2.還是食物鏈(通過上面兩個變種的描述,可以看出它們的適用範圍有很大重疊)

相關推薦

--演算法優化變種

一、定義並查集是一種樹型的資料結構,用於處理一些不相交集合的合併及查詢問題。 基礎的並查集能實現以下三個操作:1.建立集合;2.查詢某個元素是否在一給定集合內(或查詢一個元素所在的集合); 3.合併

Marriage Match II(二分++最大流好題)

Marriage Match II http://acm.hdu.edu.cn/showproblem.php?pid=3081 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768

(路徑壓縮基礎)uva1329 合作網路

【問題描述】   有n個結點(編號為1..n),初始時每個結點的父親都不存在。你的任務是執行一次I操作和E操作,格式如下:   I u v:把節點u的父親點設定為v,距離為|u-v|除以1000的餘數。輸入保證執行指令前u沒有父親節點。   E u:詢問u 到根接點的距

poj 高階應用——1703249219881417

http://poj.org/problem?id=1703 *#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm

(Union-Find Algorithm)看這一篇就夠了

動態連線(Dynamic connectivity)的問題 所謂的動態連線問題是指在一組可能相互連線也可能相互沒有連線的物件中,判斷給定的兩個物件是否聯通的一類問題。這類問題可以有如下抽象: 有一組構成不相交集合的物件 union: 聯通兩個物件

資料結構學習 講解(思路時間複雜度)

1、  概述 並查集(Disjoint set或者Union-find set)是一種樹型的資料結構,常用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。 2、  基本操作 並查集是一種非常簡單的資料結構,它主要涉及兩個基本操作,分別為: A. 合併兩個

Codeforces Round #541 (Div. 2)(又稱dsu拓撲排序)

每次 == auto else if microsoft 沒有 spa pan force #include<bits/stdc++.h>using namespace std;vector<int>g[2007];int fa[2007],vis[

演算法1.1POJ 1611 The Suspects

#include<bits/stdc++.h> using namespace std; const int maxn=200000; int fa[maxn]; int num[maxn]; int findset(int x) { return fa[x]==-1?x:fa[

演算法

簡要介紹: 並查集:   一種資料結構,將一個集合分為不相交的子集,在新增新的子集,合併現有子集,判斷元素所在集合時時間複雜度接近常數級。常用於求連通子圖和最小生成樹的Kruskal演算法。 操作:   makeSet: &nbs

7-10 排座位(演算法

7-10 排座位(25 分) 佈置宴席最微妙的事情,就是給前來參宴的各位賓客安排座位。無論如何,總不能把兩個死對頭排到同一張宴會桌旁!這個艱鉅任務現在就交給你,對任何一對客人,請編寫程式告訴主人他們是否能被安排同席。 輸入格式: 輸入第一行給出3個正整數:N(≤100),即前來參宴的

演算法模板

並查集通過一個一維陣列來實現,它的本質是維護一個森林,剛開始的時候,森林的每個點都是孤立的,也可以理解為每個點就是一棵只有一個節點的樹,之後通過一些條件,逐漸將這些樹合併成一棵大樹。其實合併的過程就是“認爹”的過程。在“認爹”的過程中,要遵守“靠左”原則和“擒賊先擒王”的原則。在每次判斷兩個節點是否

演算法

#include<stdio.h> int pre[10]; int find(int x) /*查詢祖先節點*/ { int r=x; while(pre[r]!=r) { r=pre[r]; } int i=x,j; while(i!=r)

資料結構與演算法 --- 帶路徑壓縮的加權演算法

#include <iostream> using namespace std; class UF { private: int N; // 節點數 int count; // 連通分支數 int *id; // 儲存節點

演算法介紹

我們在一些應用當中,經常會遇到將n個不同的元素分成一組不相交的集合,例如某省調查城鎮交通狀況,得到現有城鎮道路統計表,當我們知道每條道路直接連通的城鎮時,問最少還需要建設多少條道路才能使全省任何兩個城鎮間都可以實現交通。類似這種應用,經常需要進行兩種特別的操作:尋找包含給定

史上最淺顯易懂的演算法

並查集是我暑假從高手那裡學到的一招,覺得真是太精妙的設計了。以前我無法解決的一類問題竟然可以用如此簡單高效的方法搞定。不分享出來真是對不起party了。(party:我靠,關我嘛事啊?我跟你很熟麼?) 首先在地圖上給你若干個城鎮,這些城鎮都可以看作點,然後告訴你哪些對

演算法的簡介與演算法實現

並查集(Union-find Sets)是一種非常精巧而實用的資料結構,它主要用於處理一些不相交集合的合併問題。一些常見的用途有求連通子圖、求最小生成樹的 Kruskal 演算法和求最近公共祖先(Least Common Ancestors, LCA)等。 使用並查集時,首先會存在一組不相交的動態集合 S=

牛客練習賽39 D 動態連通塊+ X bitset 優化

alt reat lambda stream hid inf view puts tchar https://ac.nowcoder.com/acm/contest/368/D 題意 小T有n個點,每個點可能是黑色的,可能是白色的。小T對這張圖的定義了白連通塊和黑連通塊

基於簡單的路徑壓縮的演算法

void merge(int fir,int sec)  {     int i=fir,j=sec;     while(person[i].parent!=i)     {         person[i].parent=person[person[i].parent].parent;        

簡單易懂的演算法以及實戰演練

[TOC](文章目錄) # 前言 並查集演算法適用於處理一些不相交集合的合併及查詢問題。對於這一類的問題使用並查集,不但節省了空間,而且大大縮短了執行時間。 基本的並查集很好寫出一個模板,對於一些特殊的題目也能很好對並查集進行變形,接下來來看一下引例瞭解一下並查集 # 一、引例 男生寢室關係

POJ 1456 Supermarket(貪心演算法可用優化)

題意:         有n件商品需要賣,每件商品由(p,t)描述。其中p表示該商品被賣出可獲得的利潤,t表示該商品被賣出的截止時間。時間從1開始計時,每件商品被賣出的話需要佔用1個時間單位。如果某件商品的t=3,那麼該商品最多隻能在時間1,時間2或時間3 這3個時間點上賣