圖的生成樹(森林)(克魯斯卡爾Kruskal算法和普里姆Prim算法)、以及並查集的使用

圖的連通性問題:無向圖的連通份量和生成樹,全部頂點均由邊鏈接在一塊兒,但不存在迴路的圖。算法

設圖 G=(V, E) 是個連通圖,當從圖任一頂點出發遍歷圖G 時,將邊集 E(G) 分紅兩個集合 T(G) 和 B(G)。其中 T(G)是遍歷圖時所通過的邊的集合,B(G) 是遍歷圖時未通過的邊的集合。顯然,G1(V, T) 是圖 G 的極小連通子圖,即子圖G1 是連通圖 G 的生成樹。數組

深度優先生成森林網絡

  右邊的是深度優先生成森林:數據結構

連通圖的生成樹不必定是惟一的不一樣的遍歷圖的方法獲得不一樣的生成樹;從不一樣的頂點出發可獲得不一樣的生成樹。
連通圖自己就是連通份量,其中頂點集+遍歷通過的邊=生成樹。
非連通圖的生成森林不必定是惟一的。
非連通圖各個連通份量的頂點集+遍歷時通過的邊=若干顆生成樹(生成森林)

最小生成樹 
給定一個無向網絡,在該網的全部生成樹中,使得各邊權數之和最小的那棵生成樹稱爲該網的最小生成樹。優化

問題的提出:要在 n 個城市間創建交通網,要考慮的問題如何在保證 n 點連通的前題下最節省經費? spa

如何求連通圖的最小生成樹?3d

構造最小生成樹的算法不少,其中多數算法都利用了一種稱之爲 MST 的性質。blog

MST 性質:設 N = (V, E)  是一個連通網,U 是頂點集 V 的一個非空子集。若邊 (u, v) 是一條具備最小權值的邊,其中u∈U,v∈V-U,則必存在一棵包含邊 (u, v) 的最小生成樹。排序

方法一:普里姆 (Prim) 算法。遞歸

算法思想:

一、設 N=(V, E) 是連通網,TE 是N 上最小生成樹中邊的集合。初始令 U={u0}, (u0屬於V ), TE={ }。
二、在全部 u屬於U, v屬於V-U 的邊 (u, v)屬於E 中,
找一條代價最小的邊 (u0, v0)。
將 (u0, v0) 併入集合 TE,同時 v0 併入 U。
 
三、
重複上述操做直至 U=V 爲止,則 T=(V, TE) 爲 N 的最

小生成樹。

 

總得來講,普里姆算法就是以樹爲單位,找最小的權邊,特色是針對無向圖!只和頂點有關,和邊無關,適用於稠密圖。算法時間複雜度爲 O(n^2)

如圖:普里姆算法求最小生成樹

初始令 U={u0}, (u0屬於V ), TE={ }。

   

在全部 u屬於U, v屬於V-U 的邊 (u, v)屬於E 中,找一條代價最小的邊 (u0, v0)。將 (u0, v0) 併入集合 TE,同時 v0 併入 U。

   

重複上述操做直至 U=V 爲止,則 T=(V, TE) 爲 N 的最 小生成樹。

    

繼續

    

最後,遍歷完

    

Prim算法的實現  

頂點集合如何表示?最小邊如何選擇?一個頂點加入U集合如何表示?以下面的例子:
當U集合中加入一個新頂點時,V-U集合中的頂點到U的最小代價邊可能會更新,k 表明最終選擇的頂點,k=3,表明選擇是v3這個頂點,由於1-3代價是最小的=1
選取了 v3,以後,繼續以最新的樹爲單位,來找最小的權值邊,經過看和哪一個頂點鏈接。
k=6,表明選擇是v6這個頂點,由於3-6代價是最小的=4,在全部的和最新的樹鄰接的頂點中,權值最小的邊。
選取 v6以後
繼續以最新的樹爲單位,找臨近的頂點,看哪條邊的權值最小,找到6-4這條邊,權值=2
新的樹如圖
繼續以最新的樹爲單位,找臨近的頂點,看哪條邊的權值最小,找到3-2這條邊,權值=5
新的樹如圖
繼續以最新的樹爲單位,找臨近的頂點,看哪條邊的權值最小,找到2-5這條邊,權值=3
直到全部頂點所有併入生成樹以後,程序結束
 

方法二:克魯斯卡爾 (Kruskal) 算法。

使用了並查集,直接從邊中找到不成環的最小的權邊(最簡單的求最小生成樹的算法),特色:只針對無向圖,包好普里姆算法,都是隻針對無向圖。

算法思想:

一、設連通網  N = (V, E ),令最小生成樹初始狀態爲只有 n 個頂點而無邊的非連通圖 T=(V, { }),每一個頂點自成一個連通份量。
二、 在 E 中選取代價最小的邊,若該邊依附 的頂點落在 T 中不一樣的連通份量上(即: 不能造成環),則將此邊加入到 T 中;否 則,捨去此邊,選取下一條代價最小的邊。
三、依此類推,直至 T 中全部頂點都在同一連通份量上爲止。

最小生成樹可能不唯一(包括普里姆算法都是同樣的道理)

把全部的邊按照權值升序排列,從最小邊開始(不能造成迴路),選取,組成最小生成樹。直到全部的邊併入則結束(不是頂點!) 克魯斯卡爾算法主要在排序邊的權值序列的時候最費時間,他的算法時間複雜度和排序算法有關,而排序算法的時間複雜度和圖的邊 e 有關係,和頂點 v 沒有關係。故適用於稀疏圖。(而普里姆算法適合稠密圖
下面是圖解步驟:
按照升序,找出權值的排序序列:1 2 3 4 5 5 5 6 6 6
注意選取權值最小的邊的時候,不要造成迴路
按照權值的升序排列的順序查找選取合適的邊
繼續,按照權值的升序排列的順序查找選取合適的邊
注意選取5的時候,避免環的生成,便可
直到全部的邊都併入便可。
那麼在克魯斯卡爾算法裏,經過找合適的邊,該如何避免造成迴路呢?換句話說,如何判斷是否造成了迴路?

使用並查集能夠判斷是否造成了迴路,kruskal算法用到了一種貪心策略,首先要把邊集數組以邊的權值從小到大排序,而後一條邊一條邊的查找,若是邊的兩個端點不在一個集合內,則將此邊添加到正在生長的樹林中,併合並兩個端點所在的集合,直到最小生成樹已生成完畢。

並查集:
是一種樹型的數據結構,用於處理一些不相交集合(Disjoint Sets)的合併及查詢問題。經常在使用中以森林來表示。集就是讓每一個元素構成一個單元素的集合,也就是按必定順序將屬於同一組的元素所在的集合合併。

並查集是一種很是簡單的數據結構,它主要涉及兩個基本操做,分別爲:

A. 合併兩個不相交集合

B. 判斷兩個元素是否屬於同一個集合

1)合併兩個不相交集合(Union(x,y))

合併操做很簡單:先設置一個數組Father[x],在克魯斯卡爾算法裏,須要使用雙親存儲結構,表示x的「父親」的編號。那麼,合併兩個不相交集合的方法就是,找到其中一個集合最父親的父親(也就是最久遠的祖先),將另一個集合的最久遠的祖先的父親指向它。

通俗的說,就是把其中一個樹的根,做爲另外一個樹的根結點的一個孩子結點便可。

上圖爲兩個不相交集合,合併後能夠看出:Father(b)=Father(g)=f 結點

2)判斷兩個元素是否屬於同一集合(Find_Set(x)),本操做可轉換爲尋找兩個元素的最久遠祖先是否相同。能夠採用遞歸實現。

並查集的優化問題

尋找祖先時,咱們通常採用遞歸查找,可是當元素不少亦或是整棵樹變爲一條鏈時,每次Find_Set(x)都是O(n)的複雜度。爲了不這種狀況,咱們需對路徑進行壓縮,即當咱們通過」遞推」找到祖先節點後,」回溯」的時候順便將它的子孫節點都直接指向祖先,這樣之後再次Find_Set(x)時複雜度就變成O(1)了,以下圖所示。可見,路徑壓縮方便了之後的查找。

回到克魯斯卡爾算法,使用並查集來實現判斷迴路的生成否

好比從 v1開始(一共是 v一、v二、v三、v四、v五、v6),則開始把 v1-v6做爲各個單根樹,以森林來表示,讓每一個元素構成一個個的單元素的集合,須要使用數組表示,存儲方式就是雙親存儲結構(方便找到共同的父親)。

每次使用並查集,將後入的邊上的另外一個結點做爲孩子結點,而沒有加入的結點仍是去作爲單根的樹:

如圖所示,上圖,該選取權值=5的邊了,此時有兩個樹

   和   

若是選取3-4或者1-4這兩條邊的任意一個,單根樹是不會產生根相同的情形的,而加入的(做爲孩子的根),必定會找到共同祖先的,這樣就能夠發現迴路的存在! 而選取2-3這條邊的話,在並查集中,就不會查出共同的祖先,也就是沒有環的造成。

通俗的說,就是經過兩個元素所在的結點推出跟結點,若根相同,則爲同一個集合,不然不是同一個集合(也就是不造成迴路)

相關文章
相關標籤/搜索