日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

并查集(并茶几)

發(fā)布時(shí)間:2023/12/3 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 并查集(并茶几) 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

并查集(并茶幾)的應(yīng)用

一、What‘s that?

并查集是一種樹型的數(shù)據(jù)結(jié)構(gòu),用于處理一些不相交集合(Disjoint Sets)的合并及查詢問題。常常在使用中以森林來表示。 ——百度百科

二、How to uphold

0.我們的需求

一開始我們擁有一個(gè)集族(集合的集合),集族中所有集合不相交,每個(gè)集合有且僅有一個(gè)元素。

百度百科告訴我們 :
并查集至少需要支持兩種操作:

1.合并某兩個(gè)集合a,b。

2.詢問兩個(gè)元素x,y是否屬于同一集合。

1.可行的想法

我們能夠想到用樹的結(jié)構(gòu)存儲這樣一個(gè)集合,每一次的合并就相當(dāng)于將其中一棵樹的根的父親改為另一子樹的根的編號。

記錄一個(gè)值f[i]表示i的父親結(jié)點(diǎn)(當(dāng)i為根時(shí),將父親視為自己,也就是f[i]=i)。
所以我們有一個(gè)性質(zhì):若f[i]=i,則i為此樹的根
初始時(shí)所有的點(diǎn)的f[i]=i。

2.初始化

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

3.查找樹根

那么如何尋找一個(gè)結(jié)點(diǎn)所在的樹的根呢?
Q:只要沿著f[i]一直向上跳到f[i]=i,此時(shí)的i結(jié)點(diǎn)便是樹根

int find(int x){ return f[x]==x?x:find(f[x]);} //1.如果f[x]==x,返回樹根為x; 2.如果f[x]!=x,繼續(xù)搜索x的祖先f[x]是否為樹根

維護(hù)合并

接下來維護(hù)集合的合并操作。
例如我們需要合并2和6所在的集合:

那么只需要將2子樹的根結(jié)點(diǎn)(1結(jié)點(diǎn)),與6子樹的根結(jié)點(diǎn)(5結(jié)點(diǎn))連接即可:

void Union(int x,int y) {int xx=find(x),yy=find(y); //找到兩樹的根 if (xx!=yy) { f[yy]=xx; } //如果兩樹的根不同,合并兩棵樹 }

5.優(yōu)化原始并茶幾

這就是并查集的“初始版本”了。
但我們發(fā)現(xiàn)它每一次的詢問時(shí)間復(fù)雜度可能達(dá)到O(n)。
因?yàn)楫?dāng)數(shù)據(jù)退化為一條鏈的時(shí)候,每一次對鏈尾的find的次數(shù)是O(size)

a.按秩合并

所以這樣一個(gè)數(shù)據(jù)結(jié)構(gòu)在我們看來并不優(yōu),嘗試著優(yōu)化。
我們發(fā)現(xiàn)一棵較高的樹x與一棵較低的樹y合并時(shí),應(yīng)該從較高的樹向較低的樹連邊,使其合并。是為按秩合并。(f[y]=x)

void Union(int x,int y) {int xx=find(x),yy=find(y); if (xx!=yy) { if (rank[xx]>rank[yy]) f[yy]=xx; //按秩合并,rank[xx]表示xx樹的高度 else f[xx]=yy; } }

這一優(yōu)化據(jù)說能將時(shí)間復(fù)雜度降低為O(logn)

b.路徑壓縮

還有一個(gè)更簡單且高效的優(yōu)化:路徑壓縮
我們發(fā)現(xiàn)我們并不需要知道我們?nèi)绾窝刂鴉[i]向上跳,我們只需要找到最終的root就能夠完成任務(wù)了。所以我們把f[i]的定義改為記錄i的祖先結(jié)點(diǎn)的編號,在我們每一次find樹根的時(shí)候,更新f[i]的值為樹根,如此一來,當(dāng)我們再次find(i)時(shí),就能一步找到樹根的位置。

int find(int x){ return f[x]=(f[x]==x?x:find(f[x])); } //將得到的值直接賦予f[x]即可

這樣路徑壓縮優(yōu)化過后,每一次find時(shí)間復(fù)雜度理論上為O(1),union的時(shí)間也是O(1),這樣我們就完成了一個(gè)能夠維護(hù)部分集合操作的優(yōu)秀數(shù)據(jù)結(jié)構(gòu)。
一般情況下,路徑壓縮后的時(shí)間復(fù)雜度已經(jīng)能達(dá)到O(1),所以通常不會寫較為麻煩的按秩合并,so以下的所有代碼舍棄按秩合并的優(yōu)化。)

三、How to use it?

并查集是一個(gè)短小精悍的數(shù)據(jù)結(jié)構(gòu),因此在各類競賽中的出現(xiàn)頻率還是比較高的,并查集也有一些巧妙的思路與方法。

一般的并查集能夠維護(hù):

  • 子樹內(nèi)信息。(通常在樹根上維護(hù))
  • 從子結(jié)點(diǎn)到根的信息。
  • 1.首先來一道裸題:親戚

    問題描述
    若某個(gè)家族人員過于龐大,要判斷兩個(gè)是否是親戚,確實(shí)還很不容易,現(xiàn)在給出某個(gè)親戚關(guān)系圖,求任意給出的兩個(gè)人是否具有親戚關(guān)系。
    規(guī)定
    x和y是親戚,y和z是親戚,那么x和z也是親戚。如果x,y是親戚,那么x的親戚都是y的親戚,y的親戚也都是x的親戚。

    數(shù)據(jù)輸入:
    第一行:三個(gè)整數(shù)n,m,p,(n<=5000,m<=5000,p<=5000),分別表示有n個(gè)人,m個(gè)親戚關(guān)系,詢問p對親戚關(guān)系。
    以下m行:每行兩個(gè)數(shù)Mi,Mj,1<=Mi,Mj<=N,表示Ai和Bi具有親戚關(guān)系。
    接下來p行:每行兩個(gè)數(shù)Pi,Pj,詢問Pi和Pj是否具有親戚關(guān)系。
    數(shù)據(jù)輸出:
    P行,每行一個(gè)’Yes’或’No’。表示第i個(gè)詢問的答案為“具有”或“不具有”親戚關(guān)系
    樣例:
    input
    6 5 3
    1 2
    1 5
    3 4
    5 2
    1 3
    1 4
    2 3
    5 6
    output
    Yes
    Yes
    No

    solution:

    一題并查集裸題,只是為了演示整體代碼。明顯只是維護(hù)集合的合并,詢問結(jié)點(diǎn)連通性。

    #include <vector> #include <list> #include <map> #include <set> #include <deque> #include <queue> #include <stack> #include <bitset> #include <algorithm> #include <functional> #include <numeric> #include <utility> #include <sstream> #include <iostream> #include <iomanip> #include <cstdio> #include <cmath> #include <cstdlib> #include <cctype> #include <string> #include <cstring> #include <ctime> #include <cassert> #include <string.h> //#include <unordered_set> //#include <unordered_map>#define MP(A,B) make_pair(A,B) #define PB(A) push_back(A) #define SIZE(A) ((int)A.size()) #define LEN(A) ((int)A.length()) #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define fi first #define se secondusing namespace std;template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; } template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }typedef long long ll; typedef unsigned long long ull; typedef long double lod; typedef pair<int,int> PR; typedef vector<int> VI;const lod eps=1e-11; const lod pi=acos(-1); const int oo=1<<30; const ll loo=1ll<<62; const int mods=1e9+7; const int INF=0x3f3f3f3f;//1061109567 const int MAXN=20005; /*--------------------------------------------------------------------*/ //請省略以上部分int f[MAXN]; inline int read() {int f=1,x=0; char c=getchar();while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }return x*f; } int find(int x){ return f[x]=(f[x]==x?x:find(f[x])); } void Union(int x,int y) {int xx=find(x),yy=find(y); if (xx!=yy) f[xx]=yy; } void init(int n){ for (int i=1;i<=n;i++) f[i]=i; } int main() {int n=read(),m=read(),Case=read();init(n);for (int i=1;i<=m;i++){int x=read(),y=read();Union(x,y);}while (Case--){int x=read(),y=read();if (find(x)==find(y)) puts("Yes");else puts("No");}return 0; }

    2.一道稍稍不同的題:POJ1988 Cube Staking

    Description
    Farmer John and Betsy are playing a game with N (1 <= N <= 30,000)identical cubes labeled 1 through N. They start with N stacks, each containing a single cube. Farmer John asks Betsy to perform P (1<= P <= 100,000) operation. There are two types of operations:
    moves and counts.

    • In a move operation, Farmer John asks Bessie to move the stack containing cube X on top of the stack containing cube Y.
    • In a count operation, Farmer John asks Bessie to count the number of cubes on the stack with cube X that are under the cube X and report that value.

    Write a program that can verify the results of the game.

    Input

    • Line 1: A single integer, P
    • Lines 2…P+1: Each of these lines describes a legal operation. Line 2 describes the first operation, etc. Each line begins with a ‘M’ for a move operation or a ‘C’ for a count operation. For move operations, the line also contains two integers: X and Y.For count operations, the line also contains a single integer: X.

    Note that the value for N does not appear in the input file. No move operation will request a move a stack onto itself.

    Output
    Print the output from each of the count operations in the same order as the input file.

    Sample Input
    6
    M 1 6
    C 1
    M 2 4
    M 2 6
    C 3
    C 4

    Sample Output
    1
    0
    2
    Source
    USACO 2004 U S Open

    Solution

    題意:
    有兩種操作:

  • 將一堆立方體x堆在另一堆立方體y上面。
  • 詢問x下面有多少個(gè)立方體。
  • 這一題需要維護(hù)兩個(gè)額外的信息:

  • r[i]表示從i結(jié)點(diǎn)到根節(jié)點(diǎn)一共有多少結(jié)點(diǎn)(i到根的距離+1)
  • num[i]表示子樹一共有多少結(jié)點(diǎn)。
  • 只需要稍稍更改find的過程即可。

    #include <vector> #include <list> #include <map> #include <set> #include <deque> #include <queue> #include <stack> #include <bitset> #include <algorithm> #include <functional> #include <numeric> #include <utility> #include <sstream> #include <iostream> #include <iomanip> #include <cstdio> #include <cmath> #include <cstdlib> #include <cctype> #include <string> #include <cstring> #include <ctime> #include <cassert> #include <string.h> //#include <unordered_set> //#include <unordered_map>#define MP(A,B) make_pair(A,B) #define PB(A) push_back(A) #define SIZE(A) ((int)A.size()) #define LEN(A) ((int)A.length()) #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define fi first #define se secondusing namespace std;template<typename T>inline bool upmin(T &x,T y) { return y<x?x=y,1:0; } template<typename T>inline bool upmax(T &x,T y) { return x<y?x=y,1:0; }typedef long long ll; typedef unsigned long long ull; typedef long double lod; typedef pair<int,int> PR; typedef vector<int> VI;const lod eps=1e-11; const lod pi=acos(-1); const int oo=1<<30; const ll loo=1ll<<62; const int mods=1e9+7; const int INF=0x3f3f3f3f;//1061109567 const int MAXN=30005; /*--------------------------------------------------------------------*/ //請省略以上部分int f[MAXN<<1],r[MAXN<<1],num[MAXN<<1]; inline int read() {int f=1,x=0; char c=getchar();while (c<'0'||c>'9') { if (c=='-') f=-1; c=getchar(); }while (c>='0'&&c<='9') { x=(x<<3)+(x<<1)+c-'0'; c=getchar(); }return x*f; } inline char readc() {char c=getchar();while (!isalnum(c)) c=getchar();return c; } inline int find(int x) {if (f[x]==x) return x;int tmp=f[x];f[x]=find(f[x]);r[x]+=r[tmp];return f[x]; } int main() {int Case=read();for (int i=1;i<=MAXN;i++) f[i]=i,num[i]=1,r[i]=0;for (int i=1;i<=Case;i++){char c=readc();if (c=='M'){int x=read(),y=read();int xx=find(x),yy=find(y);if (xx!=yy){f[yy]=xx;r[yy]=num[xx];num[xx]+=num[yy];}}else {int x=read();int xx=find(x);printf("%d\n",num[xx]-r[x]-1);}} return 0; }

    本文總結(jié)

    以上是一些并查集的簡單應(yīng)用,但也是一些精妙并查集技巧操作的基礎(chǔ)。

    若已經(jīng)熟練掌握這些基礎(chǔ),那么可以學(xué)習(xí)一些其他更精妙并查集的方法:

  • 并查集的結(jié)點(diǎn)刪除
  • 帶權(quán)并查集(偏移向量、補(bǔ)集應(yīng)用 Ps:名字很多 )
  • 并查集的擴(kuò)展域
  • 這些內(nèi)容以后博主會慢慢補(bǔ)充。

    總結(jié)

    以上是生活随笔為你收集整理的并查集(并茶几)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。