算法與數(shù)據(jù)結(jié)構(gòu)系列之[并查集-上]

1.概述

并查集是一種樹形的數(shù)據(jù)結(jié)構(gòu)璃弄,但是這種樹很特殊纤掸,每棵樹都是從子節(jié)點(diǎn)指向父節(jié)點(diǎn)的政己,在使用中也常常以森林來表示掏愁,用于解決一些不相交集合的合并和查詢問題歇由。

上面的概念有點(diǎn)抽象托猩,其實(shí)并查集就是用來解決連接問題印蓖,并查集的查詢其實(shí)就是判斷兩個(gè)元素是否屬于同一個(gè)集合,兩個(gè)元素屬于同一個(gè)集合京腥,那么他們就是連接的,否則不連接溅蛉。并查集的并其實(shí)就是將不在同一個(gè)集合的兩個(gè)元素合并公浪,使其處于同一集合他宛,在樹中也就是表現(xiàn)的兩個(gè)元素的節(jié)點(diǎn)連在一起或連在同一個(gè)父節(jié)點(diǎn)上。下面用圖示說明此問題欠气。

圖一

上圖一數(shù)字 1—9各自都為一個(gè)集合厅各,都是一棵只有根節(jié)點(diǎn)的樹,相互沒有連接预柒,這九棵樹組成森林队塘。

上圖二4和6組合,4為根節(jié)點(diǎn)宜鸯。 1,憔古、2、5組合成一棵樹淋袖,3鸿市、8、9即碗、7組合成一棵樹焰情,但是1、2剥懒、5是按深度優(yōu)先組合内舟,3、8初橘、9谒获、7是按廣度優(yōu)先組合,廣度優(yōu)先組合比深度優(yōu)先組合后樹的深度要小壁却,查找時(shí)效率較高批狱。

圖二

上圖中如果將圖一中的5和4組合,5和6組合展东,其結(jié)果都是圖二赔硫,因?yàn)?和4組合就是將5所在樹的根節(jié)點(diǎn)2指向4所在樹的根節(jié)點(diǎn),4本身就是根節(jié)點(diǎn)盐肃,即將2指向4即可爪膊。5和6組合也是將5所在的根節(jié)點(diǎn)2指向6所在樹的根節(jié)點(diǎn)4,其實(shí)是完全一樣的砸王。

2.并查集的實(shí)現(xiàn)

1.數(shù)組實(shí)現(xiàn):用數(shù)組存儲集合的id,兩個(gè)元素集合id相同說明在同一個(gè)集合推盛,不同則不在同一個(gè)集合。初始時(shí)每一個(gè)元素對應(yīng)一個(gè)集合id,每一個(gè)元素都屬于一個(gè)不同的集合谦铃,將兩個(gè)元素組合耘成,就是遍歷數(shù)組將這兩個(gè)元素的集合id設(shè)置為同一個(gè)集合id,此時(shí)的時(shí)間復(fù)雜度為O(n)。查找時(shí)瘪菌,只需要返回查找元素的集合id即可撒会,所以查找的時(shí)間復(fù)雜度為O(1),因此师妙,這種并查樹的實(shí)現(xiàn)也成為:Quick Find诵肛。

圖三

java代碼:

UF.java(接口定義)

public interface UF {
    int getSize();
    boolean isConnected(int p,int q);
    void unionElements(int p,int q);
}

實(shí)現(xiàn)代碼

public class UnionFind1 implements UF {

    int[] id;  //存放集合id

    public UnionFind1(int size){
        id = new int[size];
        for (int i = 0; i < id.length; i++) {
            id[i] = i;  //初始化時(shí)每一個(gè)元素對應(yīng)一個(gè)集合id,id數(shù)組中的每一個(gè)元素都不相同,每一個(gè)元素都沒有和其他元素合并
        }
    }

    @Override
    public int getSize() {
        return id.length;
    }

    //查找索引為i的集合id
    private int find(int i){
        if(i < 0 || i>= id.length)
            throw new IllegalArgumentException("非法索引");
        return id[i];
    }

    //判斷p和q是否連接
    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    //將p和q連接
    @Override
    public void unionElements(int p, int q) {
        int pID = find(p);
        int qID = find(q);

        if(pID == qID)
            return;

        for (int i = 0; i < id.length; i++) {
            if(id[i] == pID)
                id[i] = qID;
        }
    }
}

2.使用樹實(shí)現(xiàn)并查集:由子節(jié)點(diǎn)指向父節(jié)點(diǎn)的樹默穴,將父節(jié)點(diǎn)對應(yīng)的元素存儲在數(shù)組中怔檩,初始時(shí)父節(jié)點(diǎn)對應(yīng)元素的數(shù)組中每個(gè)元素都不相同,表示每個(gè)節(jié)點(diǎn)都是父節(jié)點(diǎn)蓄诽,都沒有相連薛训。兩個(gè)元素組合時(shí),將其對應(yīng)的父節(jié)點(diǎn)的數(shù)組元素設(shè)置為相同即可若专,此時(shí)合并和查下的時(shí)間復(fù)雜度都為O(h),h為樹的深度许蓖,相比較組合時(shí)要比數(shù)組實(shí)現(xiàn)的O(n)要快概漱,因此這種并查集的實(shí)現(xiàn)也稱作:Quick Union

圖四
圖五

java代碼:

public class UnionFind2 implements UF{

    int[] parent;

    public UnionFind2(int size){
        parent = new int[size];
        for (int i = 0; i < size; i++) {
            parent[i] = i;
        }
    }

    @Override
    public int getSize() {
        return parent.length;
    }

    //查找i對應(yīng)的集合編號
    //時(shí)間復(fù)雜度為O(h),h為樹的高度
    private int find(int i){
        if(i < 0 || i>= parent.length)
            throw new IllegalArgumentException("非法索引");
        while (i != parent[i])
            i = parent[i];
        return i;
    }

    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

    //合并操作
    //時(shí)間復(fù)雜度為O(h),h為樹的高度
    @Override
    public void unionElements(int p, int q) {
        int pRoot = find(p);
        int qRoot = find(q);
        if(pRoot == qRoot)
            return;
        parent[pRoot] = qRoot;
    }
}

接下來大致的測一下用數(shù)組實(shí)現(xiàn)和用樹實(shí)現(xiàn)并查集的速度差異:

測試代碼:

public class Test {
    private static double testUF(UF uf,int m){
        int size = uf.getSize();
        Random random = new Random();

        long startTime = System.nanoTime();

        for (int i = 0; i < m; i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.unionElements(a,b);
        }

        for (int i = 0; i < m; i++) {
            int a = random.nextInt(size);
            int b = random.nextInt(size);
            uf.isConnected(a,b);
        }

        long endTime = System.nanoTime();

        return (endTime - startTime) / 1000000000.0;
    }

    public static void main(String[] args) {
        int size = 10000;
        int m = 10000;
        UnionFind1 unionFind1 = new UnionFind1(size);
        System.out.println("UnionFind1: " + testUF(unionFind1,m) + " s");
        UnionFind2 unionFind2 = new UnionFind2(size);
        System.out.println("UnionFind2: " + testUF(unionFind2,m) + " s");
        /*UnionFind3 unionFind3 = new UnionFind3(size);
        System.out.println("UnionFind3: " + testUF(unionFind3,m) + " s");
        UnionFind4 unionFind4 = new UnionFind4(size);
        System.out.println("UnionFind4: " + testUF(unionFind4,m) + " s");
        UnionFind5 unionFind5 = new UnionFind5(size);
        System.out.println("UnionFind5: " + testUF(unionFind5,m) + " s");
        UnionFind6 unionFind6 = new UnionFind6(size);
        System.out.println("UnionFind6: " + testUF(unionFind6,m) + " s");*/
    }
}

上面的測試代碼執(zhí)行了兩個(gè)方法:uf.unionElements(a,b)和 uf.isConnected(a,b)减俏。對于數(shù)組實(shí)現(xiàn)的uf.isConnected(a,b)方法時(shí)間復(fù)雜度為O(1), uf.isConnected(a,b)方法復(fù)雜度為O(n),執(zhí)行次數(shù)是size次撮竿,樹實(shí)現(xiàn)的兩個(gè)方法的時(shí)間復(fù)雜度都為O(h),執(zhí)行兩個(gè)方法的整體速度要好于數(shù)組實(shí)現(xiàn)技掏。

執(zhí)行結(jié)果:


圖六

將size和調(diào)用次數(shù)m都賦值為100000時(shí)掉丽,樹實(shí)現(xiàn)的整體速度就會下降悯搔,因?yàn)閟ize越大佃蚜,樹的深度越大夭咬,兩個(gè)O(h)的復(fù)雜度使得整體效率變差趋箩。

圖七

3.總結(jié)

這篇介紹了并查集的概述和兩種實(shí)現(xiàn)方法赃额,雖然當(dāng)操作數(shù)變大時(shí),樹實(shí)現(xiàn)的并查集會相應(yīng)變慢叫确,但常用的還是第二種方法跳芳,用樹來實(shí)現(xiàn)并查集,其實(shí)用樹來實(shí)現(xiàn)并查集還有一些優(yōu)化方法竹勉,能夠使其速度遠(yuǎn)大于用數(shù)組實(shí)現(xiàn)的速度飞盆。
下一篇將會介紹幾種優(yōu)化并查集的方法。

本人微信公眾號次乓,點(diǎn)關(guān)注吓歇,不迷路
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市票腰,隨后出現(xiàn)的幾起案子城看,更是在濱河造成了極大的恐慌,老刑警劉巖杏慰,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件测柠,死亡現(xiàn)場離奇詭異炼鞠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鹃愤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門簇搅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來完域,“玉大人软吐,你說我怎么就攤上這事∫魉埃” “怎么了凹耙?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長肠仪。 經(jīng)常有香客問我肖抱,道長,這世上最難降的妖魔是什么异旧? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任意述,我火速辦了婚禮,結(jié)果婚禮上吮蛹,老公的妹妹穿的比我還像新娘荤崇。我一直安慰自己,他們只是感情好潮针,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布术荤。 她就那樣靜靜地躺著,像睡著了一般每篷。 火紅的嫁衣襯著肌膚如雪瓣戚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天焦读,我揣著相機(jī)與錄音子库,去河邊找鬼。 笑死矗晃,一個(gè)胖子當(dāng)著我的面吹牛仑嗅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喧兄,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼无畔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吠冤?” 一聲冷哼從身側(cè)響起浑彰,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拯辙,沒想到半個(gè)月后郭变,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颜价,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年诉濒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了周伦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡未荒,死狀恐怖专挪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情片排,我是刑警寧澤寨腔,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站率寡,受9級特大地震影響迫卢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冶共,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一乾蛤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捅僵,春花似錦家卖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至醋奠,卻和暖如春榛臼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背窜司。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工沛善, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人塞祈。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓金刁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親议薪。 傳聞我的和親對象是個(gè)殘疾皇子尤蛮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容