最小生成樹,Prim演算法與Kruskal演算法,408方向,思路與實現分析
最小生成樹,老生常談了,生活中也總會有各種各樣的問題,在這裡,我來帶你一起分析一下這個演算法的思路與實現的方式吧~~
在考研中呢,最小生成樹雖然是隻考我們分析,理解就行,但我們還是要知道底層是怎麼實現的,話不多說,進入正題~~
什麼是生成樹?什麼是最小生成樹
總所周知,對於一個無向連通圖,我們想把他看成一個樹的話,那麼就不能太亂,也就引出了,如果對於一個生成樹(不唯一,滿足條件即可),如果砍去它的一條邊,則會變成非連通圖,如果加上一條邊則會形成一個迴路,也就是包含圖的全部頂點的一個極小連通子圖,如下所示~~
因此,對於頂點數為n的圖,生成樹應含有n-1條邊~~
那麼,最小生成樹又是什麼呢?
當邊帶權時,我們想要一個生成樹,使得帶權路徑和最小,那麼這樣的生成樹就被我們稱作最小生成樹(不唯一),也就是邊的權值之和最小的生成樹(MST,Minimum-Spanning-Tree)~~
那麼怎麼求一個最小生成樹呢?
沒錯,我們相求一個最小生成樹,我們的前輩大牛也想過這個問題,也就引出了兩個演算法,一個是Prim演算法(普里姆),和Kruskal演算法(克魯斯卡爾)~~
先給一個案例我們來分析一下,經典的修路~~
我們想要使得我們這個小鎮全都連通,但又花費經濟最少(虛線邊數值代表所需金錢,也就是權值),該怎麼修呢?先給出答案,接下來我們用兩個演算法分別分析一下思路以及底層實現~~
Prim演算法(普里姆)思路分析與底層實現
Prim演算法:從選取的某一個頂點開始,每次將代價最小的新頂點納入生成樹,直到所有頂點都納入為止~~
思路分析:也就是並查集內加新頂點,為了方便理解,我們把並查集每次都納入的頂點看成一個在一個圈裡加入新頂點,不屬於圈裡的即為連通的頂點,如圖所示演算法思路~~
底層實現:我們這裡定義兩個陣列一個為isJoin[]和lowCost[]陣列,isJoin[]用來標記是否已加入樹,lowCost[]用來表示各節點加入當前樹時的最低代價~~
如第一個步驟的時候,只有c點加入了樹,那麼我們就當c點已經加入了樹,也就是說isJoin[6]變為了如表所示(為了方便我們把陣列下標換成我們的字母abcdef)~~
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 0 | 0 | 0 | 0 | 0 |
此時我們的lowCost[6]對應就應為(沒有邊直接連通則為無窮):
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 1 | 5 | 4 | 6 | 4 |
lowCost最小為1,也就是C與A連線所需代價最小,所以當第1步結束時,我們將A連入樹中,修改對應isJoin[6]和lowCost[6]~~
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 1 | 0 | 0 | 0 | 0 |
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 0 | 5 | 4 | 6 | 4 |
權:1~~
lowCost最小為4,也就是(D或者F)與圈AC連線所需代價最小,所以當第2步結束時,我們將F連入樹中,修改對應isJoin[6]和lowCost[6]~~
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 1 | 0 | 0 | 0 | 1 |
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 0 | 5 | 2 | 6 | 0 |
權:1+4~~
因為現在要和圈ACF連線,D如果想代價最小就可以選擇DF連線(2),而不是以前的DC連線(4),2代價更小,這就是lowCost[]用來表示各節點加入當前樹時的最低代價的意思~~
重複此過程~~
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 1 | 0 | 1 | 0 | 1 |
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 0 | 5 | 0 | 6 | 0 |
權:1+4+2~~
接下來
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 1 | 1 | 1 | 0 | 1 |
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 0 | 0 | 0 | 3 | 0 |
權:1+4+2+5~~
最後
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
isJoin[6] | 1 | 1 | 1 | 1 | 1 | 1 |
C | A | B | D | E | F | |
---|---|---|---|---|---|---|
lowCost[6] | 0 | 0 | 0 | 0 | 0 | 0 |
權:1+4+2+5+3=15~~
Kruskal演算法(克魯斯卡爾)思路分析與底層實現
雙生關係,Prim是每次選一個連到樹裡代價最小的頂點構成一個樹,Kruskal是每次選一條權值最小的邊,使這條邊兩端連通(如果原本就已經連通就不選),直到所有結點都連通~~
思路分析:上圖~~
底層實現:並查集~~
按權值排序~~
weight | 1 | 2 |
---|---|---|
1 | A | C |
2 | D | F |
3 | B | E |
4 | D | C |
4 | C | F |
5 | A | D |
5 | B | C |
6 | A | B |
6 | C | E |
6 | E | F |
依次往下看,1和2兩個點是否屬於同一集合(先規定所有頂點都屬於不同的集合,如果兩個頂點相連,則這兩個頂點構成了一個集合)~~
如第一次,weight為1,A和C為不同集合,相連,通過~~(集合AC,B,D,E,F)
第二次,weight為2,D和F為不同集合,相連,通過~~(集合AC,DF,B,E)
第三次,weight為3,B和E為不同集合,相連,通過~~(集合AC,DF,BE)
第四次,weight為4,D和C雖然DF是一個集合,AC是一個集合,但DF和AC不是一個集合裡的不連通,所以相連,通過~~(集合ACDF,BE)
第五次,weight為4,C和F同屬於ACDF集合裡,所以不相連,不通過~~(集合ACDF,BE)
第六次,weight為5,A和D同屬於ACDF集合裡,所以不相連,不通過~~(集合ACDF,BE)
第七次,weight為5,B和C不屬於同一個集合裡,所以相連,通過~~(集合ABCDEF)
至此成功~~
最小生成樹還是簡單的,為我們後面學習最短路徑做基礎,這裡大家好好看看,下週我就來談一談最短路徑的幾個演算法,這兩者很像,但要區分開來~~