数据结构--图(Graph)详解(四)
數(shù)據(jù)結(jié)構(gòu)–圖(Graph)詳解(四)
文章目錄
- 數(shù)據(jù)結(jié)構(gòu)--圖(Graph)詳解(四)
- 一、圖中幾個NB的算法
- 1.普里姆算法(Prim算法)求最小生成樹
- 2.克魯斯卡爾算法(Kruskal算法)求最小生成樹
- 3.拓?fù)渑判蛩惴?/li>
- 4.迪杰斯特拉(Dijkstra算法)算法
- 5.弗洛伊德算法
一、圖中幾個NB的算法
1.普里姆算法(Prim算法)求最小生成樹
普里姆算法(Prim算法)求最小生成樹:https://blog.csdn.net/wolfGuiDao/article/details/107588112
該算法從頂點的角度為出發(fā)點,時間復(fù)雜度為O(n2),更適合與解決邊的綢密度更高的連通網(wǎng)。
2.克魯斯卡爾算法(Kruskal算法)求最小生成樹
- 克魯斯卡爾算法,從邊的角度求網(wǎng)的最小生成樹,時間復(fù)雜度為O(eloge)。和普里姆算法恰恰相反,更適合于求邊稀疏的網(wǎng)的最小生成樹。
- 對于任意一個連通網(wǎng)的最小生成樹來說,在要求總的權(quán)值最小的情況下,最直接的想法就是將連通網(wǎng)中的所有邊按照權(quán)值大小進(jìn)行升序排序,從小到大依次選擇。
- 由于最小生成樹本身是一棵生成樹,所以需要時刻滿足以下兩點:
- 生成樹中任意頂點之間有且僅有一條通路,也就是說,生成樹中不能存在回路;
- 對于具有 n 個頂點的連通網(wǎng),其生成樹中只能有 n-1 條邊,這 n-1 條邊連通著 n 個頂點。
- 連接 n 個頂點在不產(chǎn)生回路的情況下,只需要 n-1 條邊。
- 所以克魯斯卡爾算法的具體思路是:將所有邊按照權(quán)值的大小進(jìn)行升序排序,然后從小到大一一判斷
- 條件為:如果這個邊不會與之前選擇的所有邊組成回路,就可以作為最小生成樹的一部分;反之,舍去。
- 直到具有 n 個頂點的連通網(wǎng)篩選出來 n-1 條邊為止。篩選出來的邊和所有的頂點構(gòu)成此連通網(wǎng)的最小生成樹。
- 判斷是否會產(chǎn)生回路的方法為:在初始狀態(tài)下給每個頂點賦予不同的標(biāo)記,對于遍歷過程的每條邊,其都有兩個頂點,判斷這兩個頂點的標(biāo)記是否一致,如果一致,說明它們本身就處在一棵樹中,如果繼續(xù)連接就會產(chǎn)生回路;如果不一致,說明它們之間還沒有任何關(guān)系,可以連接(也可以使用并查集來判斷)。
假設(shè)遍歷到一條由頂點 A 和 B 構(gòu)成的邊,而頂點 A 和頂點 B 標(biāo)記不同,此時不僅需要將頂點 A 的標(biāo)記更新為頂點 B 的標(biāo)記,還需要更改所有和頂點 A 標(biāo)記相同的頂點的標(biāo)記,全部改為頂點 B 的標(biāo)記。
- 例如,使用克魯斯卡爾算法找圖 1 的最小生成樹的過程為:
- 首先,在初始狀態(tài)下,對各頂點賦予不同的標(biāo)記(用顏色區(qū)別),如下圖所示:
- 對所有邊按照權(quán)值的大小進(jìn)行排序,按照從小到大的順序進(jìn)行判斷,首先是(1,3)
- 由于頂點 1 和頂點 3 標(biāo)記不同,所以可以構(gòu)成生成樹的一部分,遍歷所有頂點,將與頂點 3 標(biāo)記相同的全部更改為頂點 1 的標(biāo)記,如(2)所示:
- 其次是(4,6)邊,兩頂點標(biāo)記不同,所以可以構(gòu)成生成樹的一部分,更新所有頂點的標(biāo)記為:
- 其次是(2,5)邊,兩頂點標(biāo)記不同,可以構(gòu)成生成樹的一部分,更新所有頂點的標(biāo)記為:
- 然后最小的是(3,6)邊,兩者標(biāo)記不同,可以連接,遍歷所有頂點,將與頂點 6 標(biāo)記相同的所有頂點的標(biāo)記更改為頂點 1 的標(biāo)記:
- 繼續(xù)選擇權(quán)值最小的邊,此時會發(fā)現(xiàn),權(quán)值為 5 的邊有 3 個,其中(1,4)和(3,4)各自兩頂點的標(biāo)記一樣,如果連接會產(chǎn)生回路,所以舍去
- 而(2,3)標(biāo)記不一樣,可以選擇,將所有與頂點 2 標(biāo)記相同的頂點的標(biāo)記全部改為同頂點 3 相同的標(biāo)記:
- 當(dāng)選取的邊的數(shù)量相比與頂點的數(shù)量小 1 時,說明最小生成樹已經(jīng)生成。所以最終采用克魯斯卡爾算法得到的最小生成樹為(6)所示
3.拓?fù)渑判蛩惴?/h3>
- 拓?fù)渑判蛑傅氖菍⒂邢驘o環(huán)圖(又稱“DAG”圖)中的頂點按照圖中指定的先后順序進(jìn)行排序。
- 在圖論中,由一個有向無環(huán)圖的頂點組成的序列,當(dāng)且僅當(dāng)滿足下列條件時,稱為該圖的一個拓?fù)渑判?/strong>
- 每個頂點出現(xiàn)且只出現(xiàn)一次;
- 若存在一條從頂點 A 到頂點 B 的路徑,那么在序列中頂點 A 出現(xiàn)在頂點 B 的前面。
-
例如,圖 1 中的兩個圖都是有向無環(huán)圖,都可以使用拓?fù)渑判驅(qū)D中的頂點進(jìn)行排序
-
兩個圖形的區(qū)別是:左圖中的 V2 和 V3 之間沒有明確的前后順序;而右圖中任意兩個頂點之間都有前后順序。
-
所以,左圖中頂點之間的關(guān)系被稱為“偏序”關(guān)系;右圖中頂點之間的關(guān)系被稱為”全序“關(guān)系。
在有向無環(huán)圖中,弧的方向代表著頂點之間的先后次序,例如從 V1 指向 V2 的弧表示在進(jìn)行排序時 V1 在前, V2 在后。
全序是偏序的一種特殊情況。對于任意一個有向無環(huán)圖來說,通過拓?fù)渑判虻玫降男蛄惺紫纫欢ㄊ瞧?#xff0c;如果任意兩個頂點都具有前后順序,那么此序列是全序。
- 拓?fù)渑判虻姆椒?/strong>
對有向無環(huán)圖進(jìn)行拓?fù)渑判?#xff0c;只需要遵循兩個原則: - 在圖中選擇一個沒有前驅(qū)的頂點 V(即入度為0);
- 從圖中刪除頂點 V 和所有以該頂點為尾的弧(刪除該頂點和所有以它為起點的有向邊)。
例如,在對圖 1 中的左圖進(jìn)行拓?fù)渑判驎r的步驟如圖 2 所示
有向無環(huán)圖如果頂點本身具有某種實際意義,例如用有向無環(huán)圖表示大學(xué)期間所學(xué)習(xí)的全部課程,每個頂點都表示一門課程,有向邊表示課程學(xué)習(xí)的先后次序
例如要先學(xué)《程序設(shè)計基礎(chǔ)》和《離散數(shù)學(xué)》,然后才能學(xué)習(xí)《數(shù)據(jù)結(jié)構(gòu)》。所以用來表示某種活動間的優(yōu)先關(guān)系的有向圖簡稱為“AOV網(wǎng)”。
- 進(jìn)行拓?fù)渑判驎r,首先找到?jīng)]有前驅(qū)的頂點 V1,如圖 2(1)所示;
- 在刪除頂點 V1 及以 V1 作為起點的弧后,繼續(xù)查找沒有前驅(qū)的頂點,此時, V2 和 V3 都符合條件,可以隨機(jī)選擇一個,例如圖 2(2) 所示,選擇 V2
- 然后繼續(xù)重復(fù)以上的操作,直至最后找不到?jīng)]有前驅(qū)的頂點。
所以,針對圖 2 來說,拓?fù)渑判蜃詈蟮玫降男蛄杏袃煞N:
V1 -> V2 -> V3 -> V4 V1 -> V3 -> V2 -> V4- 如果頂點之間只是具有偏序關(guān)系,那么拓?fù)渑判虻慕Y(jié)果肯定不唯一;如果頂點之間是全序關(guān)系,那么拓?fù)渑判虻玫降男蛄形ㄒ弧?/strong>
- 拓?fù)渑判虻腃語言實現(xiàn)
- 大致思路為:首先通過鄰接表將 AOV 網(wǎng)進(jìn)行存儲,由于拓?fù)渑判虻恼麄€過程中,都是以頂點的入度為依據(jù)進(jìn)行排序,所以需要根據(jù)建立的鄰接表統(tǒng)計出各頂點的入度。
- 在得到各頂點的入度后,首先找到入度為 0 的頂點作為拓?fù)渑判虻钠鹗键c
- 然后查找以該頂點為起始點的所有頂點,如果入度為 1,說明如果刪除前一個頂點后,該頂點的入度為 0,為拓?fù)渑判虻南乱粋€對象。
4.迪杰斯特拉(Dijkstra算法)算法
由荷蘭計算機(jī)科學(xué)家艾茲赫爾·戴克斯特拉在1956年提出。戴克斯特拉算法使用了廣度優(yōu)先搜索解決賦權(quán)有向圖的單源最短路徑問題。
5.弗洛伊德算法
暑假,小哼準(zhǔn)備去一些城市旅游。有些城市之間有公路,有些城市之間則沒有,如下圖。為了節(jié)省經(jīng)費以及方便計劃旅程,小哼希望在出發(fā)之前知道任意兩個城市之前的最短路程。
上圖中有4個城市8條公路,公路上的數(shù)字表示這條公路的長短。
請注意這些公路是單向的。我們現(xiàn)在需要求任意兩個城市之間的最短路程,也就是求任意兩個點之間的最短路徑。這個問題這也被稱為“多源最短路徑”問題。
- 現(xiàn)在需要一個數(shù)據(jù)結(jié)構(gòu)來存儲圖的信息,我們?nèi)匀豢梢杂靡粋€4*4的矩陣(二維數(shù)組e)來存儲。
- 比如1號城市到2號城市的路程為2,則設(shè)e[1][2]的值為2。
- 2號城市無法到達(dá)4號城市,則設(shè)置e[2][4]的值為∞。
- 另外此處約定一個城市自己是到自己的也是0,例如e[1][1]為0,具體如下。
-
現(xiàn)在回到問題:如何求任意兩點之間最短路徑呢?通過之前的學(xué)習(xí)我們知道通過深度或廣度優(yōu)先搜索可以求出兩點之間的最短路徑。
-
所以進(jìn)行n2遍深度或廣度優(yōu)先搜索,即對每兩個點都進(jìn)行一次深度或廣度優(yōu)先搜索,便可以求得任意兩點之間的最短路徑。可是還有沒有別的方法呢?
-
我們來想一想,根據(jù)我們以往的經(jīng)驗,如果要讓任意兩點(例如從頂點a點到頂點b)之間的路程變短,只能引入第三個點(頂點k),并通過這個頂點k中轉(zhuǎn)即a->k->b,才可能縮短原來從頂點a點到頂點b的路程。
-
那么這個中轉(zhuǎn)的頂點k是1~n中的哪個點呢?甚至有時候不只通過一個點,而是經(jīng)過兩個點或者更多點中轉(zhuǎn)會更短,即a->k1->k2b->或者a->k1->k2…->k->i…->b。
-
比如上圖中從4號城市到3號城市(4->3)的路程e[4][3]原本是12。如果只通過1號城市中轉(zhuǎn)(4->1->3),路程將縮短為11(e[4][1]+e[1][3]=5+6=11)。
-
其實1號城市到3號城市也可以通過2號城市中轉(zhuǎn),使得1號到3號城市的路程縮短為5(e[1][2]+e[2][3]=2+3=5)。
-
所以如果同時經(jīng)過1號和2號兩個城市中轉(zhuǎn)的話,從4號城市到3號城市的路程會進(jìn)一步縮短為10。
-
通過這個的例子,我們發(fā)現(xiàn)每個頂點都有可能使得另外兩個頂點之間的路程變短。好,下面我們將這個問題一般化。
-
當(dāng)任意兩點之間不允許經(jīng)過第三個點時,這些城市之間最短路程就是初始路程,如下。
- 假如現(xiàn)在只允許經(jīng)過1號頂點,求任意兩點之間的最短路程,應(yīng)該如何求呢?
- 只需判斷e[i][1] + e[1][j]是否比e[i][j]要小即可。
- e[i][j]表示的是從i號頂點到j(luò)號頂點之間的路程。e[i][1] + e[1][j]表示的是從i號頂點先到1號頂點,再從1號頂點到j(luò)號頂點的路程之和。其中i是1~n循環(huán),j也是1~n循環(huán),代碼實現(xiàn)如下。
- 在只允許經(jīng)過1號頂點的情況下,任意兩點之間的最短路程更新為:
-
通過上圖我們發(fā)現(xiàn):在只通過1號頂點中轉(zhuǎn)的情況下,3號頂點到2號頂點(e[3][2])、4號頂點到2號頂點(e[4][2])以及4號頂點到3號頂點(e[4][3])的路程都變短了。
-
接下來繼續(xù)求在只允許經(jīng)過1和2號兩個頂點的情況下任意兩點之間的最短路程。
-
如何做呢?我們需要在只允許經(jīng)過1號頂點時任意兩點的最短路程的結(jié)果下,再判斷如果經(jīng)過2號頂點是否可以使得i號頂點到j(luò)號頂點之間的路程變得更短。
-
即判斷e[i][2]+e[2][j]是否比e[i][j]要小,代碼實現(xiàn)為如下。
- 在只允許經(jīng)過1和2號頂點的情況下,任意兩點之間的最短路程更新為:
-
通過上圖得知,在相比只允許通過1號頂點進(jìn)行中轉(zhuǎn)的情況下,這里允許通過1和2號頂點進(jìn)行中轉(zhuǎn),使得e[1][3]和e[4][3]的路程變得更短了。
-
同理,繼續(xù)在只允許經(jīng)過1、2和3號頂點進(jìn)行中轉(zhuǎn)的情況下,求任意兩點之間的最短路程。任意兩點之間的最短路程更新為:
- 最后允許通過所有頂點作為中轉(zhuǎn),任意兩點之間最終的最短路程為:
總結(jié)
以上是生活随笔為你收集整理的数据结构--图(Graph)详解(四)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据结构--图(Graph)详解(三)
- 下一篇: 进程和线程基础知识(已经是最详细的啦)