1. 程式人生 > >並查集(模板&典型例題整理)

並查集(模板&典型例題整理)

並查集,並查集是一種樹形結構,又叫“不相交集合”,保持了一組不相交的動態集合,每個集合通過一個代表來識別,代表即集合中的某個成員,通常選擇根做這個代表。
也就是說,並查集是用來處理不相交集合型別問題,如問不相交集合有幾個。給定節點,找到該節點所在集合元素個數,當然這只是水題。並查集會與其他演算法結合著考,如LCA中的tarjian演算法。後續部落格會整理。
並查集,顧名思義,主要分三部分。
一:合併:給出兩點關係,如果屬於同一集合,進行merge
二:查:在合併時,需要先寫出查,即找到該點的祖先點
三:集:merge後,將新加入的點的祖先點更新
然後,點集就因為共同的祖先點被分為不同的集合啦
結合例題更容易理解
裸題模板:
hdu1232暢通工程
暢通工程
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 52315 Accepted Submission(s): 27902

Problem Description
某省調查城鎮交通狀況,得到現有城鎮道路統計表,表中列出了每條道路直接連通的城鎮。省政府“暢通工程”的目標是使全省任何兩個城鎮間都可以實現交通(但不一定有直接的道路相連,只要互相間接通過道路可達即可)。問最少還需要建設多少條道路?

Input
測試輸入包含若干測試用例。每個測試用例的第1行給出兩個正整數,分別是城鎮數目N ( < 1000 )和道路數目M;隨後的M行對應M條道路,每行給出一對正整數,分別是該條道路直接連通的兩個城鎮的編號。為簡單起見,城鎮從1到N編號。
注意:兩個城市之間可以有多條道路相通,也就是說
3 3
1 2
1 2
2 1
這種輸入也是合法的
當N為0時,輸入結束,該用例不被處理。

Output
對每個測試用例,在1行裡輸出最少還需要建設的道路數目。

Sample Input

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

Sample Output

1
0
2
998

就是說將所有獨立的集合連線起來還需要幾條路,那隻要找到獨立集合個數-1就可以啦

#include<stdio.h>
int father[1005];
int Find(int x)
{

    while(x!=father[x])
        x=father[x];
    return x;    
}
void Combine(int
a,int b) { int fa=Find(a); int fb=Find(b); if(fa!=fb) { father[fa]=fb; } } int main() { int n,m; int i; int a,b; while(~scanf("%d",&n)) { if(n==0) break; scanf("%d",&m); int sum=0; for(i=1;i<=n;i++) father[i]=i; for(i=0;i<m;i++) { scanf("%d%d",&a,&b); Combine(a,b); } for(i=1;i<=n;i++) { if(father[i]==i) sum++; } printf("%d\n",sum-1); } return 0; }

再看一個引申題,poj1611
The Suspects
Time Limit: 1000MS Memory Limit: 20000K
Total Submissions: 37109 Accepted: 17992

Description
Severe acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized as a global threat in mid-March 2003. To minimize transmission to others, the best strategy is to separate the suspects from others.
In the Not-Spreading-Your-Sickness University (NSYSU), there are many student groups. Students in the same group intercommunicate with each other frequently, and a student may join several groups. To prevent the possible transmissions of SARS, the NSYSU collects the member lists of all student groups, and makes the following rule in their standard operation procedure (SOP).
Once a member in a group is a suspect, all members in the group are suspects.
However, they find that it is not easy to identify all the suspects when a student is recognized as a suspect. Your job is to write a program which finds all the suspects.

Input
The input file contains several cases. Each test case begins with two integers n and m in a line, where n is the number of students, and m is the number of groups. You may assume that 0 < n <= 30000 and 0 <= m <= 500. Every student is numbered by a unique integer between 0 and n−1, and initially student 0 is recognized as a suspect in all the cases. This line is followed by m member lists of the groups, one line per group. Each line begins with an integer k by itself representing the number of members in the group. Following the number of members, there are k integers representing the students in this group. All the integers in a line are separated by at least one space.
A case with n = 0 and m = 0 indicates the end of the input, and need not be processed.

Output
For each case, output the number of suspects in one line.

Sample Input

100 4
2 1 2
5 10 13 11 12 14
2 0 1
2 99 2
200 2
1 5
5 1 2 3 4 5
1 0
0 0

Sample Output

4
1
1
題意是,一些學生被分組,0號可能感染病毒,跟他同一集合的也可能感染,那麼給出幾個分組,問感染的人數。所以,0作為祖先節點,只要與0同集合就將人數陣列增加。

#include<stdio.h>
#define MAX 30005

int a[MAX],pre[MAX];

int find(int x)
{
    if(x!=pre[x])

        //找到其祖先節點 
        pre[x] = find(pre[x]);  

    //由父節點繼續向上遞迴查詢 ,並將其父節點變成找到的 
    return pre[x]; 
}
void merge(int x,int y)
{
    //分別查詢兩點的祖先節點。 
    int prex = find(x);
    int prey = find(y);

    //如果二者的祖先節點不一致,那麼任意讓二者中某一個認另一個為祖先,保證同集合。

    if(prex == prey)
    {
        return ;
    } 
    //應該是祖先節點進行組合。而不是當前節點! 
    pre[prey] = prex;  
    a[prex] += a[prey];     
}
int main()
{
    int n,m;
    int k,x,y;
    while(~scanf("%d%d",&n,&m))
    {
        if(n==0&&m==0)
        {
            return 0;
        } 
        for(int i=0;i<n;i++)
        {
            //先將自身作為祖先節點。 
            pre[i] = i;  
            a[i] = 1; 
        }
        for(int i=0;i<m;i++)
        {
            //給出集合每個集合人數,以及第一個人的編號 
            scanf("%d%d",&k,&x);
            k--;
            while(k--)
            {
                scanf("%d",&y);
                merge(x,y); 
            }    
        } 
        printf("%d\n",a[find(0)]);

    } 
    return 0;
}

在find函式採用了狀態壓縮,將每個點的父節點都更新為合併的祖先節點,這樣查詢速度將更快。

int find(int x)
{
    if(x!=pre[x])

        //找到其祖先節點,並將其父節點變成找到的祖先節點
        pre[x] = find(pre[x]);  

    //由父節點繼續向上遞迴查詢
    return pre[x]; 
}