圖(二)——最小生成樹、最短路徑問題
第十一周課堂學習內容消化——圖(二)(三)
本周課堂學習內容
- 圖的遍歷
- 最小生成樹
- 最短路徑問題
- 活動頂點與活動邊問題
本周課堂中未理解透徹的地方
- Prim算法
- Kruskal算法
- Dijkstra算法
- Floyd算法
學習內容總結
貪心算法
研究完Prim算法、Kruskal算法和Dijkstra算法之后,我發現他們都屬於貪心算法。因此,在要搞懂這兩種算法之前,應該先弄明白貪心算法是什么。
一、簡述貪心算法
所謂貪心算法是指,在對問題求解時,總是做出在當前看來是最好的選擇。也就是說,不從整體最優上加以考慮,他所做出的僅是在某種意義上的局部最優解。
貪心算法沒有固定的算法框架,算法設計的關鍵是貪心策略的選擇。必須注意的是,貪心算法不是對所有問題都能得到整體最優解,選擇的貪心策略必須具備無后效性,即某個狀態以后的過程不會影響以前的狀態,只與當前狀態有關。
所以對所采用的貪心策略一定要仔細分析其是否滿足無后效性。
二、貪心算法的基本思路
- 建立數學模型來描述問題。
- 把求解的問題分成若干個子問題。
- 對每一子問題求解,得到子問題的局部最優解。
- 把子問題的解局部最優解合成原來解問題的一個解。
三、貪心算法適用的問題
貪心策略適用的前提是:局部最優策略能導致產生全局最優解。
實際上,貪心算法適用的情況很少。一般,對一個問題分析是否適用於貪心算法,可以先選擇該問題下的幾個實際數據進行分析,就可做出判斷。
動態規划
Floyd算法是一種典型的動態規划。在研究Floyd算法之前,我們先來研究一下這個動態規划。
一、簡述動態規划
動態規划過程是:每次決策依賴於當前狀態,又隨即引起狀態的轉移。一個決策序列就是在變化的狀態中產生出來的,所以,這種多階段最優化決策解決問題的過程就稱為動態規划。
動態規划的基本思想與分治法類似,也是將待求解的問題分解為若干個子問題(階段),按順序求解子階段,前一子問題的解,為后一子問題的求解提供了有用的信息。在求解任一子問題時,列出各種可能的局部解,通過決策保留那些有可能達到最優的局部解,丟棄其他局部解。依次解決各子問題,最后一個子問題就是初始問題的解。
由於動態規划解決的問題多數有重疊子問題這個特點,為減少重復計算,對每一個子問題只解一次,將其不同階段的不同狀態保存在一個二維數組中。
與分治法最大的差別是:適合於用動態規划法求解的問題,經分解后得到的子問題往往不是互相獨立的(即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解)。
二、動態規划的基本思路
- 分析最優解的性質,並刻畫其結構特征。
- 遞歸的定義最優解。
- 以自底向上或自頂向下的記憶化方式(備忘錄法)計算出最優值
- 根據計算最優值時得到的信息,構造問題的最優解
三、動態規划適用的問題
能采用動態規划求解的問題的一般要具有3個性質:
- 最優化原理:如果問題的最優解所包含的子問題的解也是最優的,就稱該問題具有最優子結構,即滿足最優化原理。
- 無后效性:即某階段狀態一旦確定,就不受這個狀態以后決策的影響。也就是說,某狀態以后的過程不會影響以前的狀態,只與當前狀態有關。
- 有重疊子問題:即子問題之間是不獨立的,一個子問題在下一階段決策中可能被多次使用到。(該性質並不是動態規划適用的必要條件,但是如果沒有這條性質,動態規划算法同其他算法相比就不具備優勢)
Prim算法和Kruskal算法
弄明白貪心算法怎么回事之后,接下啦就開始挨個分析這幾個算法啦~
一、算法簡述
Prim算法和Kruskal算法都是實現最小生成樹的算法,都屬於貪心算法。
首先,先弄清最小生成樹的概念與特點:
最小生成樹及各邊的權值總和最小的生成樹。其中,若最小生成樹n個頂點,那么他的邊就有n-1個。若每個邊的權值都是最小的,那么整體加起來就一定是最小的,這里並不存在后繼型,因此在實現最小生成樹時可以運用貪心算法。
接下來是Prim算法和Kruskal算法實現最小生成樹的方法:
Prim算法:
核心思想:將點分為兩撥,已經加入最小生成樹的,和未加入的。找到未加入中距離集合最近的點,添加該點,修改其它點到集合的距離,直到所有結點都加入到最小生成樹。
Kruskal算法:
核心思想:按照權值從小到大的順序選擇n-1條邊,並保證這n-1條邊不構成回路。其中判斷是否回路為核心。
二、算法過程演示
以課上中給出的圖為例:
Prim算法:
- 隨機選擇一個點作為初始點。這里我選擇頂點A。
- 找到A周圍權值最小的邊(這里最小的是權值為1的A-C邊),加入對應的頂點C。
- 將A-C看做整體后,再尋找這個整體周圍權值最小的邊。在這里是C-F邊,加入頂點F。
- 重復以上步驟,由於
若最小生成樹n個頂點,那么他的邊就有n-1個
,因此循環n-1次即可
Kruskal算法:
- 首先將各邊按照權值有小到大排序
- 判斷回路:
- A、B、C、D、E、F的下標分別為0,1,2,3,4,5
- 他們的初始值均先賦為-1
- 從最小的權值邊開始,兩個頂點隨便設定一個作為根結點,將根結點的值減一,另一個頂點值改為根結點的下標
- 在加邊的時候,若兩個點的值相同,或者其中一個的值為根結點的下標,則會構成回路,應該排除這樣的情況。
- 重復以上的步驟,至少需要循環判斷n-1次,一直到找到n-1條符合條件的邊才停止。
Dijkstra算法和Floyd算法
一、算法簡述
Dijkstra算法和Floyd算法都是廣度優先搜索的算法,目的是解決最短路徑問題。其中,Dijkstra算法也是一種貪心算法,而Floyd算法更多的是一種動態規划。
Dijkstra算法:
Dijkstra算法為最短路徑中的單源最短路徑。主要特點是以起始點為中心向外層層擴展,直到擴展到終點為止。注意該算法要求圖中不存在負權邊。解決的是圖中任意一個頂點到其他頂點的最短距離。
設G為賦權有向圖或無向圖,G邊上的權均非負。
求G中從頂點u0到其余點的最短路。
S:具有永久標號的頂點集。
對每個頂點,定義兩個標記(l(v) , z(v)),其中,
l(v):表示從頂點u0到v的一條路的權。
z(v):v的父親點,用以確定最短路的路線。
算法的過程就是在每一步改進這兩個標記,使最終l(v)為從頂點u0到v的最短路的權。輸入為帶權鄰接矩陣W。
Floyd算法:
Floyd算法是各頂點對間最短路徑算法,解決任意兩點間的最短路徑的一種算法,可以正確處理有向圖或負權的最短路徑問題,同時也被用於計算有向圖的傳遞閉包。
D(i , j):i 到 j 的距離
R(i , j):i 到 j 之間的插入點
輸入帶權鄰接矩陣W,
- 賦初值:對所有 i , j ,d ( i , j ) ← w ( i , j ) , r ( i , j ) ← j , k←1。
- 更新 d ( i , j ) , r ( i , j ):對所有 i , j , 若 d ( i , k ) + d ( k , j ) <d ( i , j ) ,則
d ( i , j ) ← d ( i , j ) + d ( i , j ), r ( i , j ) ← k - 若 k = v ,停止;否則 k ← k + 1,轉2