算法1之並查集

算法1之並查集

並查集:即包含 合併集合查找集合中的元素 兩種操作的樹型數據結構, 三個字已經涵蓋了其功能。常常在使用中以森林來表示。

一、並查集思想

1.1 算法思想

如果將集合中的元素當成是樹上的不同的節點,那麼,判斷兩個元素是否屬於同一個集合的問題,就變成了他們所在樹的根是否爲同一根的問題。

1.2 路徑壓縮

同一棵樹存在不同的枝幹,枝幹上有不同層次的葉節點(即我們所說的元素)。爲了查快查找某個葉子節點的根節點的速度,查找時將元素 x 到根節點 root 上的所有葉子節點的 Parent(父節點)設爲根節點。稱該優化的方法爲路徑壓縮

經過路徑壓縮優化後,平均複雜度可視爲 Ackerman 函數的反函數,實際應用中可粗略認爲其是一個常數。

1.3 問題描述

以親戚爲例,或許你不會知道,某個朋友是你的親戚,他可能是你的曾祖父的外公的女婿的外甥女的表姐的孫子。

通過家族圖譜,可以很輕鬆判斷兩個人是否爲親戚。但當兩個人的最近公共祖先與他們相隔好幾代,使得家族十分龐大,那麼檢驗兩人的親戚關係就會變得很困難。通過寫程序推出 Marry 和 Ben 是否爲親戚。

說明: 這是一個非常經典的並查集例子。用集合的思路,對每個人建立一個集合,開始的時候集合元素就是這個人本身,表示開始時,不知道任何人是他的親戚。以後,每次給出一個親戚關係時,就開始進行集合合併。這樣實時地獲取到當前狀態下的集合關係。

並查集

1.4 主要操作

並查集主要操作爲:一初始、二合併、三查找、四判斷

  • 初始化並查集(InitUnionFind)
  • 合併兩個不相交的集合(Union)
    兩個元素,分別找到他們的根節點,然後將其中一個元素的根節點的父親指向另外的一個元素的根節點。
  • 查找某元素的根節點(Find)
    查找一個元素的根節點,parent--->parent--->parent.....。當查找元素根節點途徑的元素很多時,使用路徑壓縮 優化算法。直接將該元素的父親指向根節點或者祖先。
  • 判斷兩個元素是否屬於同一集合(isConnected)
    判斷兩個元素是否屬於同一個集合,就是分別找到他們的根節點,然後判斷兩個根節點是否相等。

二、並查集例子

2.1 暢通工程

Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 70879 Accepted Submission(s): 37912

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

Hint
Hint

Huge input, scanf is recommended.

Source
浙大計算機研究生複試上機考試-2005年

Recommend
JGShining

#include<iostream>
#include<stdio.h>
using namespace std;

int pre[1010];

int unionSearch(int root)
{

    int son,tmp;
    son = root;
    /*查找根節點*/
    while(root!= pre[root])
        root = pre[root];
    /*路徑壓縮*/
    while(son!= root)
    {
        tmp = pre[son];
        pre[son] = root;
        son = tmp;
    }

    return root; // 返回跟界定啊
}

int main()
{
    int num, road, total,start, end, root1, root2;
    while(scanf("%d", &num) && num && scanf("%d", &road))
    {
        total = num-1;// num-1 個集合(元素);
        for(int i=1; i<=num; i++)
        {
            pre[i] = i; //1.初始化並查集,此時每個元素自己都是根節點
        }

        while(road--)
        {
            scanf("%d%d", &start, &end); //2.不同集合根據路徑,開始合併
            root1 = unionSearch(start); //3.查找根節點,路徑壓縮
            root2 = unionSearch(end);
            if(root1 !=root2)  //4.判斷,根節點不一樣
            {
                pre[root1] = root2; //選出一個當根節點
                total--; //集合少一個,關係少一個
            }
        }
        cout<<total<<endl; //計算剩餘的集合
    }
    return 0;
}

【推薦】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章