圖的演算法的總結

ZHUO_SIR發表於2018-06-07
圖的定義:
       很簡單,G(V,E), V、E分別表示點和邊的集合。       

圖的表示:
       主要有兩種,鄰接矩陣和鄰接表,前者空間複雜度,O(V2),後者為O(V+E)。因此,除非非常稠密的圖(邊非常多),一般後者優越於前者。

圖的遍歷:
       寬度遍歷BFS(start):    (1) 佇列Q=Empty,陣列bool visited[V]={false...}. Q.push(start);
                                             (2) while (!Q.empty()){
                                                       u = Q.pop();  visited[u] = true;   //遍歷u結點
                                                       foreach (u的每一個鄰接結點v) Q.push(v);
                                                    }   
       深度遍歷DFS(start):     (1) 棧S=Empty, 陣列bool visited[V]={false...}. S.push(start);
                                               (2) while (!S.empty()){
                                                       u = S.pop();
                                                       if (!visited[u]) visited[u] = true;   //遍歷u結點
                                                       foreach (u的每一個鄰接結點v) S.push(v);
                                                    }
       初看之下兩個演算法很相似,主要區別在於一個使用佇列,一個使用棧,最終導致了遍歷的順序截然不同。佇列是先入先出,所以訪問u以後接下來就訪問u中未訪問過的鄰接結點;而棧的後進先出,當訪問u後,壓入了u的鄰接結點,在後面的迴圈中,首先訪問u的第一個臨接點v,接下來又將v的鄰接點w壓入S,這樣接下來要訪問的自然是w了。

最小生成樹:
       (1)Prime演算法:    (1) 集合MST=T=Empty,選取G中一結點u,T.add(u)
                                  (2) 迴圈|V|-1次:
                                        選取一條這樣的邊e=min{(x,y) | x in T, y in V/T}
                                        T.add(y); MST.add(e);
                                  (3) MST即為所求
       (2) Kruskal演算法   (1) 將G中所有的邊排序並放入集合H中,初始化集合MST=Empty,初始化不相交集合T={{v1}, {v2}...}},也即T中每個點為一個集合。
                                    (2)  依次取H中的最短邊e(u,v),如果Find-Set(u)!=Find-Set(v)(也即u、v是否已經在一棵樹中),那麼Union(u,v) (即u,v合併為一個集合),MST.add(e);
                                    (3) MST即為所求

       這兩個演算法都是貪心演算法,區別在於每次選取邊的策略。證明該演算法的關鍵在於一點:如果MST是圖G的最小生成樹,那麼在子圖G'中包含的子生成樹MST' 也必然是G'的最小生成樹。這個很容易反正,假設不成立,那麼G'有一棵權重和更小的生成樹,用它替換掉MST',那麼對於G我們就找到了比MST更小的生成樹,顯然這與我們的假設(MST是最小生成樹)矛盾了。
       理解了這個關鍵點,演算法的正確性就好理解多了。對於Prime,T於V/T兩個點集都會各自有一棵生成樹,最後要連起來構成一棵大的生成樹,那麼顯然要選兩者之間的最短的那條邊了。對於Kruskal演算法,如果當前選取的邊沒有引起環路,那麼正確性是顯然的(對給定點集依次選最小的邊構成一棵樹當然是最小生成樹了),如果導致了環路,那麼說明兩個點都在該點集裡,由於已經構成了樹(否則也不可能導致環路)並且一直都是挑盡可能小的,所以肯定是最小生成樹。

最短路徑:
       這裡的演算法基本是基於動態規劃和貪心演算法的,經典演算法有很多個,主要區別在於:有的是通用的,有的是針對某一類圖的,例如,無負環的圖,或者無負權邊的圖等。
       單源最短路徑(1) 通用(Bellman-Ford演算法):
                               (2) 無負權邊的圖(Dijkstra演算法):
                               (3) 無環有向圖(DAG) :
       所有結點間最短路徑:
                               (1) Floyd-Warshall演算法:
                               (2) Johnson演算法:

相關文章