<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺談Union-Find并查集

          共 1240字,需瀏覽 3分鐘

           ·

          2022-02-26 02:29

          Union-Find并查集作為一種樹型的數(shù)據(jù)結(jié)構(gòu),用于高效進行不相交集合的合并、查詢

          abstract.png

          基本原理

          并查集的基本思路是用一個數(shù)組表示整個森林,其中樹的根節(jié)點唯一標(biāo)識了一個集合。當(dāng)我們搜索找到了某元素的的根節(jié)點時,就能確定其所屬的集合。顧名思義,對于一個并查集而言,其支持的操作方法自然包括:union、find。前者用于對指定的兩個元素建立連接,將二者歸屬為一個集合,即所謂的屬于同一個連通分量;后者用于查找指定元素所在連通分量的標(biāo)識。同時如果期望判斷兩個元素是否存在連接,即判斷并查集中的兩個元素是否存在于同一個連通分量。則可以通過find方法進行實現(xiàn)。具體地,分別查詢兩個元素的連通分量標(biāo)識,然后判斷是否相同即可。并查集常見的API操作,如下所示

          //?建立p節(jié)點、q節(jié)點之間的連接
          public?void?union(int?p,?int?q);

          //?獲取p節(jié)點所在連通分量的標(biāo)識
          public?int?find(int?p);

          //?獲取連通分量的數(shù)量
          public?int?getCount();

          //?判斷兩個節(jié)點是否存在于同一個連通分量當(dāng)中
          public?boolean?isConnected(int?p,?int?q);

          樸素實現(xiàn)

          假設(shè)存在編號分別為0~N-1的N個節(jié)點,那么在實現(xiàn)一個包含N個節(jié)點的并查集過程時。一個樸素的思想:我們可以通過一個數(shù)組進行維護。其中,數(shù)組的索引為節(jié)點編號,數(shù)組的值為該節(jié)點對應(yīng)的連通分量標(biāo)識。具體地,在union操作過程中,當(dāng)兩個節(jié)點的連通分量標(biāo)識不一致時,需要遍歷數(shù)組,將其中一個節(jié)點的連通分量標(biāo)識全部修改為其中另外一個節(jié)點的連通分量標(biāo)識。具體實現(xiàn)過程如下所示,可以看到這里直接將節(jié)點編號用來作為連通分量標(biāo)識。不難看出該實現(xiàn)非常簡單,雖然find操作非???。但對于union操作而言,效率卻不容樂觀。因為其需要遍歷整個數(shù)組

          /**
          ?*?并查集
          ?*?@author?Aaron?Zhu
          ?*?@date?2022-02-20
          ?*/

          public?class?UnionFind?{
          ????/**
          ?????*?Key:?節(jié)點;?Value:?節(jié)點對應(yīng)的連通分量標(biāo)識
          ?????*/

          ????private?int[]?id;

          ????/**
          ?????*?連通分量的數(shù)量
          ?????*/

          ????private?int?count;

          ????/**
          ?????*?構(gòu)造并查集實例
          ?????*?@param?size?節(jié)點數(shù)量
          ?????*/

          ????public?UnionFind(int?size)?{
          ????????count?=?size;
          ????????id?=?new?int[size];
          ????????for?(int?i=0;?i????????????id[i]?=?i;
          ????????}
          ????}

          ????/**
          ?????*?獲取連通分量的數(shù)量
          ?????*?@return
          ?????*/

          ????public?int?getCount()?{
          ????????return?count;
          ????}

          ????/**
          ?????*?判斷兩個節(jié)點是否存在于同一個連通分量當(dāng)中
          ?????*?@param?p
          ?????*?@param?q
          ?????*?@return
          ?????*/

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

          ????/**
          ?????*?獲取p節(jié)點所在連通分量的標(biāo)識
          ?????*?@param?p
          ?????*?@return
          ?????*/

          ????public?int?find(int?p)?{
          ????????return?id[p];
          ????}

          ????/**
          ?????*?建立p節(jié)點、q節(jié)點之間的連接
          ?????*?@param?p
          ?????*?@param?q
          ?????*/

          ????public?void?union(int?p,?int?q)?{
          ????????int?pId?=?find(p);
          ????????int?qId?=?find(q);
          ????????if(?pId?==?qId?)?{
          ????????????//?p節(jié)點、q節(jié)點已經(jīng)位于同一連通分量當(dāng)中,?故直接返回
          ????????????return;
          ????????}

          ????????//?將p節(jié)點所在連通分量的標(biāo)識全部更改為q節(jié)點所在連通分量的標(biāo)識
          ????????for(int?i=0;?i????????????if(?id[i]?==?pId?)?{
          ????????????????id[i]?=?qId;
          ????????????}
          ????????}
          ????????//?連通分量的數(shù)量減1
          ????????count--;
          ????}
          }

          一種改進版本的并查集實現(xiàn)方案,即是提高優(yōu)化union操作的時間效率。這時數(shù)組的值不再直接存儲該節(jié)點對應(yīng)的連通分量標(biāo)識,而是存儲當(dāng)前節(jié)點所在連通分量的下一個節(jié)點。顯然當(dāng)數(shù)組的索引、值相同時,此時該索引對應(yīng)的節(jié)點即是該連通分量的標(biāo)識。事實上,這就是find操作的邏輯。而對于union操作而言,同樣需要先通過find分別獲取兩個節(jié)點各自的根節(jié)點。如果不一致,則將其中一個節(jié)點的根節(jié)點指向另外一個節(jié)點的根節(jié)點即可。具體實現(xiàn)如下所示。不難看出在該實現(xiàn)下,Union操作效率得到了顯著提高。一個連通分量中的各節(jié)點實際上構(gòu)成了一種樹形結(jié)構(gòu)

          /**
          ?*?Quick?Union版本的并查集
          ?*?@author?Aaron?Zhu
          ?*?@date?2022-02-20
          ?*/

          public?class?QuickUnionUF?{
          ????/**
          ?????*?Key:?節(jié)點;?Value:?當(dāng)前節(jié)點所在連通分量的下一個節(jié)點
          ?????*/

          ????private?int[]?parent;

          ????/**
          ?????*?連通分量的數(shù)量
          ?????*/

          ????private?int?count;

          ????/**
          ?????*?構(gòu)造并查集實例
          ?????*?@param?size?節(jié)點數(shù)量
          ?????*/

          ????public?QuickUnionUF(int?size)?{
          ????????count?=?size;
          ????????parent?=?new?int[size];
          ????????for?(int?i=0;?i????????????parent[i]?=?i;
          ????????}
          ????}

          ????/**
          ?????*?獲取連通分量的數(shù)量
          ?????*?@return
          ?????*/

          ????public?int?getCount()?{
          ????????return?count;
          ????}

          ????/**
          ?????*?判斷兩個節(jié)點是否存在于同一個連通分量當(dāng)中
          ?????*?@param?p
          ?????*?@param?q
          ?????*?@return
          ?????*/

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

          ????/**
          ?????*?獲取p節(jié)點所在連通分量的標(biāo)識
          ?????*?@param?p
          ?????*?@return
          ?????*/

          ????public?int?find(int?p)?{
          ????????//?直到找到根節(jié)點
          ????????while?(p?!=?parent[p])?{
          ????????????p?=?parent[p];
          ????????}
          ????????return?parent[p];
          ????}

          ????/**
          ?????*?建立p節(jié)點、q節(jié)點之間的連接
          ?????*?@param?p
          ?????*?@param?q
          ?????*/

          ????public?void?union(int?p,?int?q)?{
          ????????int?pRoot?=?find(p);
          ????????int?qRoot?=?find(q);
          ????????if(?pRoot==qRoot?)?{
          ????????????//?p節(jié)點、q節(jié)點的根節(jié)點一樣,?故直接返回
          ????????????return;
          ????????}
          ????????//?將?p節(jié)點的根節(jié)點?指向?q節(jié)點的根節(jié)點
          ????????parent[pRoot]?=?qRoot;
          ????????//?連通分量的數(shù)量減1
          ????????count--;
          ????}
          }

          優(yōu)化

          按秩合并

          在Quick Union版本的并查集QuickUnionUF中,當(dāng)進行union操作的兩個節(jié)點并不是屬于同一連通分量時。我們需要將其中一棵樹的根節(jié)點指向另一顆樹的根節(jié)點,以實現(xiàn)將兩顆樹合并為一棵樹。但上述實現(xiàn)過程中,合并過程是隨意的。可能是將大樹的根節(jié)點指向小樹的根節(jié)點,也可能是將小樹的根節(jié)點指向大樹的根節(jié)點。這里就引出另外一個問題了——何為大樹?何為小樹?理論上來說,按樹的高度、樹中節(jié)點的數(shù)量都可以作為樹的比較標(biāo)準(zhǔn)。甚至從某種意義上來說,按樹的高度可能更為貼切。但這樣即會導(dǎo)致在同時使用路徑壓縮的場景時,樹的實際高度也會發(fā)生變化。換言之這兩種優(yōu)化策略將會不完全兼容,樹的高度實際上只是一個估計值了。故這里將樹中節(jié)點的數(shù)量定義為秩Rank,將樹中節(jié)點的數(shù)量作為樹的比較標(biāo)準(zhǔn)。因為理論上來說,樹越小、樹中節(jié)點的數(shù)量越少,則樹的高度自然也越小。而且其還能很好地與路徑壓縮優(yōu)化策略進行兼容,因為一棵樹無論怎么壓縮路徑,節(jié)點數(shù)都不會發(fā)生變化。至此,所謂按秩合并的策略就是在union操作過程中始終將 小樹的根節(jié)點 指向 大樹的根節(jié)點,以盡可能避免將二者合并為一顆高度更大的樹

          figure 1.jpeg

          路徑壓縮

          在Quick Union版本的并查集QuickUnionUF中,在不斷進行union操作后。樹的高度會越來越大,即使使用了按秩合并這一策略,也無法避免。所以一旦樹中的節(jié)點距離根節(jié)點的路徑過長,其顯然會大大降低find操作的效率。為此我們需要在find操作中引入路徑壓縮這一優(yōu)化策略,具體可分為:隔代路徑壓縮、完全路徑壓縮

          「1. 隔代路徑壓縮」

          所謂隔代路徑壓縮指的是在find操作過程中遍歷某條路徑時,不斷將當(dāng)前節(jié)點直接指向祖父節(jié)點(即父節(jié)點的父節(jié)點)??梢钥吹礁舸窂綁嚎s雖然壓縮的不夠徹底,但只需遍歷一次效率較高

          figure 2.jpeg

          「2. 完全路徑壓縮」

          如果期望將待查找節(jié)點到根節(jié)點到路徑進行徹底壓縮,即該條路徑上點全部直連根節(jié)點。這就是所謂的完全路徑壓縮。而實現(xiàn)方式上較為簡單的就是通過遞歸的方式進行實現(xiàn);否則如果期望通過非遞歸的方式實現(xiàn),則需要進行兩次遍歷。具體地:第一次遍歷先找到根節(jié)點,第二次遍歷則需將此條路徑上的所有節(jié)點全部指向根節(jié)點

          figure 3.jpeg

          優(yōu)化實現(xiàn)

          這里我們將在樸素的Quick Union版本實現(xiàn)的基礎(chǔ)上,對上述提到的兩種優(yōu)化思路進行實踐落地、集中展示

          package?com.aaron.Algo;

          /**
          ?*?優(yōu)化版本的并查集
          ?*?@author?Aaron?Zhu
          ?*?@date?2022-02-20
          ?*/

          public?class?UF2?{
          ????/**
          ?????*?Key:?節(jié)點;?Value:?當(dāng)前節(jié)點所在連通分量的下一個節(jié)點
          ?????*/

          ????public?int[]?parent;

          ????/**
          ?????*?根節(jié)點對應(yīng)的秩,?即連通分量中的節(jié)點數(shù)
          ?????*/

          ????private?int[]?rank;

          ????/**
          ?????*?連通分量的數(shù)量
          ?????*/

          ????private?int?count;

          ????/**
          ?????*?構(gòu)造并查集實例
          ?????*?@param?size?節(jié)點數(shù)量
          ?????*/

          ????public?UF2(int?size)?{
          ????????count?=?size;
          ????????parent?=?new?int[size];
          ????????rank?=?new?int[size];
          ????????for(int?i=0;?i????????????parent[i]?=?i;
          ????????????rank[i]?=?1;
          ????????}
          ????}

          ????/**
          ?????*?獲取連通分量的數(shù)量
          ?????*?@return
          ?????*/

          ????public?int?getCount()?{
          ????????return?count;
          ????}

          ????/**
          ?????*?判斷兩個節(jié)點是否存在于同一個連通分量當(dāng)中
          ?????*?@param?p
          ?????*?@param?q
          ?????*?@return
          ?????*/

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

          ????/**
          ?????*?獲取p節(jié)點所在連通分量的標(biāo)識
          ?????*?@param?p
          ?????*?@return
          ?????*?@apiNote?路徑壓縮:?隔代路徑壓縮
          ?????*/

          ????public?int?find(int?p)?{
          ????????//?直到找到根節(jié)點
          ????????while?(p?!=?parent[p])?{
          ????????????//?隔代路徑壓縮:?將當(dāng)前節(jié)點直接指向祖父節(jié)點(即父節(jié)點的父節(jié)點)
          ????????????parent[p]?=?parent[?parent[p]?];
          ????????????p?=?parent[p];
          ????????}
          ????????return?parent[p];
          ????}

          ????/**
          ?????*?獲取p節(jié)點所在連通分量的標(biāo)識
          ?????*?@param?p
          ?????*?@return
          ?????*?@apiNote?路徑壓縮:?完全路徑壓縮(迭代版本)
          ?????*/

          /*
          ????public?int?find(int?p)?{
          ????????if(?p?!=?parent[p]?)?{
          ????????????parent[p]?=?find(?parent[p]?);
          ????????}
          ????????return?parent[p];
          ????}
          */


          ????/**
          ?????*?獲取p節(jié)點所在連通分量的標(biāo)識
          ?????*?@param?p
          ?????*?@return
          ?????*?@apiNote?路徑壓縮:?完全路徑壓縮(循環(huán)版本)
          ?????*/

          /*
          ????public?int?find(int?p)?{
          ????????//?1.?找到根節(jié)點Root
          ????????int?root?=?p;
          ????????while(root!=parent[root])?{
          ????????????root?=?parent[root];
          ????????}

          ????????//?2.?循環(huán)壓縮?p->...->root?的路徑
          ????????while(?p?!=?root?)?{
          ????????????//?暫存當(dāng)前節(jié)點的下一個節(jié)點
          ????????????int?temp?=?parent[p];
          ????????????//?將當(dāng)前節(jié)點直接指向Root
          ????????????parent[p]?=?root;
          ????????????//?繼續(xù)處理下一個節(jié)點
          ????????????p?=?temp;
          ????????}

          ????????return?root;
          ????}
          */


          ????/**
          ?????*?建立p節(jié)點、q節(jié)點之間的連接
          ?????*?@param?p
          ?????*?@param?q
          ?????*/

          ????public?void?union(int?p,?int?q)?{
          ????????int?pRoot?=?find(p);
          ????????int?qRoot?=?find(q);
          ????????if(?pRoot==qRoot?)?{
          ????????????//?p節(jié)點、q節(jié)點的根節(jié)點一樣,?故直接返回
          ????????????return;
          ????????}

          ????????//?將?小樹的根節(jié)點?指向?大樹的根節(jié)點
          ????????//?同時,?更新大樹根節(jié)點對應(yīng)的節(jié)點數(shù)
          ????????if(rank[pRoot]?????????????parent[pRoot]?=?qRoot;
          ????????????rank[qRoot]?+=?rank[pRoot];
          ????????}?else?{
          ????????????parent[qRoot]?=?pRoot;
          ????????????rank[pRoot]?+=?rank[qRoot];
          ????????}
          ????????//?連通分量的數(shù)量減1
          ????????count--;
          ????}
          }

          參考文獻

          1. 算法·第4版 Robert Sedgewick、Kevin Wayne著
          瀏覽 85
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  俺来也俺去射 | 亚洲高清成人电影 | 天堂一区二区三区 | 中文不卡视频 | 校园春色亚洲App下载 |