- 最小生成樹(Minimum Span Tree):對於帶權無向連通圖。所有節點都連通且總權值最小。應用:電纜布線、網絡、電路設計
- 找V-1條邊,連接V個頂點,總權值最小
- 切分定理(Cut Property):給定任意切分,橫切邊中權值最小的邊必屬於最小生成樹
- 切分:把圖中節點分為兩部分
- 橫切邊:邊的兩個端點屬於切分的不同兩邊
- 證明:反證法,假設橫切邊中一條權值不是最小的邊屬於最小生成樹,給生成樹添加橫切邊中權值最小的邊形成環,刪掉權值不是最小的邊打破環得到新的權值更小的生成樹,與假設矛盾
- 實現:從一個點開始,不斷擴散,求出最小生成樹
main.cpp(測試有權圖)

1 #include <iostream> 2 #include <iomanip> 3 #include "SparseGraph.h" 4 #include "DenseGraph.h" 5 #include "ReadGraph.h" 6 #include "Component.h" 7 #include "Path.h" 8 #include "ShortestPath.h" 9 10 using namespace std; 11 12 int main(){ 13 14 string filename = "testG1.txt"; 15 int V = 8; 16 cout<<fixed<<setprecision(2); 17 18 // Test Weighted Dense Graph 19 DenseGraph<double> g1 = DenseGraph<double>(V,false); 20 ReadGraph<DenseGraph<double>,double> readGraph(g1, filename); 21 g1.show(); 22 cout<<endl; 23 24 // Test Weighted Dense Graph 25 SparseGraph<double> g2 = SparseGraph<double>(V,false); 26 ReadGraph<SparseGraph<double>,double> SparseGraph(g2, filename); 27 g2.show(); 28 cout<<endl; 29 30 return 0; 31 }
Edge.h

1 #ifndef INC_01_WEIGHTED_GRAPH_EDGE_H 2 #define INC_01_WEIGHTED_GRAPH_EDGE_H 3 4 #include <iostream> 5 6 using namespace std; 7 8 // 邊 9 template<typename Weight> 10 class Edge{ 11 private: 12 int a,b; // 邊的兩邊 13 Weight weight; // 邊的權值 14 public: 15 // 構造函數 16 Edge(int a, int b, Weight weight){ 17 this->a = a; 18 this->b = b; 19 this->weight = weight; 20 } 21 // 空的構造函數,所有成員變量取默認值 22 Edge(){} 23 ~Edge(){} 24 int v(){ return a; } // 返回第一個頂點 25 int w(){ return b; } // 返回第二個頂點 26 Weight wt(){ return weight;} // 返回權值 27 28 // 給定一個頂點,返回另一個頂點 29 int other(int x){ 30 assert( x == a || x == b ); 31 return x == a ? b : a; 32 } 33 34 // 輸出邊的信息 35 friend ostream& operator<<(ostream &os, const Edge &e){ 36 os<<e.a<<"-"<<e.b<<": "<<e.weight; 37 return os; 38 } 39 40 // 邊的大小比較,比較邊的權值 41 bool operator<(Edge<Weight>& e){ 42 return weight < e.wt(); 43 } 44 bool operator<=(Edge<Weight>& e){ 45 return weight <= e.wt(); 46 } 47 bool operator>(Edge<Weight>& e){ 48 return weight > e.wt(); 49 } 50 bool operator>=(Edge<Weight>& e){ 51 return weight >= e.wt(); 52 } 53 bool operator==(Edge<Weight>& e){ 54 return weight == e.wt(); 55 } 56 }; 57 58 #endif //INC_01_WEIGHTED_GRAPH_EDGE_H
ReadGraph.h

1 #include <iostream> 2 #include <string> 3 #include <fstream> 4 #include <sstream> 5 #include <cassert> 6 7 using namespace std; 8 9 template <typename Graph, typename Weight> 10 class ReadGraph{ 11 public: 12 // 從文件filename中讀取圖的信息,存進graph中 13 ReadGraph(Graph &graph, const string &filename){ 14 ifstream file(filename); 15 string line; 16 int V,E; 17 18 assert(file.is_open()); 19 20 // 讀取圖中第一行節點數和邊數 21 assert(getline(file,line)); 22 stringstream ss(line); 23 ss>>V>>E; 24 25 assert( V == graph.V() ); 26 27 // 讀取每一條邊的信息 28 for( int i = 0 ; i < E ; i ++ ){ 29 30 assert( getline(file, line) ); 31 stringstream ss(line); 32 33 int a,b; 34 Weight w; 35 ss>>a>>b>>w; 36 assert( a >= 0 && a < V ); 37 assert( b >= 0 && b < V ); 38 graph.addEdge( a , b, w ); 39 } 40 } 41 };
- Lazy Prim:MinHeap實現,復雜度O(ElogE),E為邊數
main.cpp(測試Lazy Prim)

1 #include <iostream> 2 #include <iomanip> 3 #include "SparseGraph.h" 4 #include "DenseGraph.h" 5 #include "ReadGraph.h" 6 #include "Component.h" 7 #include "Path.h" 8 #include "ShortestPath.h" 9 #include "LazyPrimMST.h" 10 11 using namespace std; 12 13 int main(){ 14 15 string filename = "testG1.txt"; 16 int V = 8; 17 SparseGraph<double> g = SparseGraph<double>(V, false); 18 ReadGraph<SparseGraph<double>,double> readGraph(g, filename); 19 20 // Test Weighted Dense Graph 21 cout << "Test Lazy Prim MST:"<< endl; 22 LazyPrimMST<SparseGraph<double>, double> lazyPrimMST(g); 23 vector<Edge<double>> mst = lazyPrimMST.mstEdge(); 24 for( int i = 0 ; i < mst.size() ; i ++ ) 25 cout << mst[i] << endl; 26 cout << "The MST weight is: "<<lazyPrimMST.result()<<endl; 27 28 cout<<endl; 29 30 return 0; 31 }
LazyPrimMST.h

1 #include "MinHeap.h" 2 3 using namespace std; 4 5 template<typename Graph, typename Weight> 6 class LazyPrimMST{ 7 private: 8 Graph &G; // 圖的引用 9 MinHeap<Edge<Weight>> pq; // 用最小堆作為優先隊列 10 vector<Edge<Weight>> mst; // 最小生成樹包含的所有邊 11 bool *marked; // 標記數組,標記節點i在運行過程中是否被訪問 12 Weight mstWeight; // 最小生成樹的權 13 14 void visit(int v){ 15 assert( !marked[v] ); 16 // 將節點v標記為訪問過 17 marked[v] = true; 18 19 typename Graph::adjIterator adj(G,v); 20 for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ) 21 if( !marked[e->other(v)] ) 22 pq.insert(*e); 23 } 24 25 public: 26 // 初始化,最差情況下所有邊都要進入最小堆 27 LazyPrimMST(Graph &graph):G(graph), pq(MinHeap<Edge<Weight>>(graph.E())){ 28 marked = new bool[G.V()]; // 頂點個數 29 for( int i = 0 ; i < G.V() ; i ++ ) 30 marked[i] = false; 31 mst.clear(); 32 33 // Lazy Prim 34 // 時間復雜度O(ElogE),E為邊數 35 visit(0); 36 while( !pq.isEmpty() ){ 37 Edge<Weight> e = pq.extractMin(); 38 // e不是橫切邊 39 if( marked[e.v()] == marked[e.w()] ) 40 continue; 41 mst.push_back( e ); 42 if( !marked[e.v()] ) 43 visit( e.v() ); 44 else 45 visit( e.w() ); 46 } 47 mstWeight = mst[0].wt(); 48 for( int i = 1 ; i < mst.size() ; i ++ ) 49 mstWeight += mst[i].wt(); 50 } 51 52 ~LazyPrimMST(){ 53 delete[] marked; 54 } 55 56 vector<Edge<Weight>> mstEdge(){ 57 return mst; 58 } 59 60 Weight result(){ 61 return mstWeight; 62 } 63 };
PrimMST.h

1 #include <iostream> 2 #include <vector> 3 #include <cassert> 4 #include "Edge.h" 5 #include "IndexMinHeap.h" 6 7 using namespace std; 8 9 template<typename Graph, typename Weight> 10 class PrimMST{ 11 private: 12 Graph &G; // 圖的引用 13 IndexMinHeap<Weight> ipq; // 最小索引堆(輔助) 14 vector<Edge<Weight>*> edgeTo; // 訪問的點所對應的邊(輔助) 15 bool *marked; // 標記數組,運行過程中節點i是否被訪問 16 vector<Edge<Weight>> mst; // 最小生成樹包含的所有邊(為什么不加*) 17 Weight mstWeight; // 最小生成樹的權值 18 void visit(int v){ 19 assert( !marked[v] ); 20 marked[v] = true; 21 22 // 遍歷 23 typename Graph::adjIterator adj(G,v); 24 for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){ 25 // 相鄰頂點 26 int w = e->other(v); 27 // 若邊的另一端未被訪問 28 if( !marked[w] ){ 29 // 如果未考慮過這個端點,將端點和與之相連的邊加入索引堆 30 if( !edgeTo[w] ){ 31 ipq.insert(w, e->wt()); 32 edgeTo[w] = e; 33 } 34 // 如果考慮過這個端點,但現在的邊比之前考慮的更短,則替換 35 else if( e->wt() < edgeTo[w]->wt() ){ 36 edgeTo[w] = e; 37 ipq.change(w, e->wt()); 38 } 39 } 40 } 41 } 42 43 public: 44 PrimMST(Graph &graph):G(graph),ipq(IndexMinHeap<double>(graph.V())){ 45 marked = new bool[G.V()]; 46 for( int i = 0 ; i < G.V() ; i ++ ){ 47 marked[i] = false; 48 edgeTo.push_back(NULL); 49 } 50 mst.clear(); 51 // Prim 52 visit(0); 53 while( !ipq.isEmpty() ){ 54 int v = ipq.extractMinIndex(); 55 assert( edgeTo[v] ); 56 mst.push_back( *edgeTo[v] ); 57 visit(v); 58 } 59 60 mstWeight = mst[0].wt(); 61 for( int i = 1 ; i < mst.size() ; i ++ ) 62 mstWeight += mst[i].wt(); 63 } 64 65 ~PrimMST(){ 66 delete[] marked; 67 } 68 69 vector<Edge<Weight>> mstEdge(){ 70 return mst; 71 } 72 73 Weight result(){ 74 return mstWeight; 75 } 76 };
- Prim:IndexMinHeap實現,復雜度O(ElogV),V為節點數
- 索引堆存放當前在最小生成樹中的節點與其他節點相連的邊
- 從起點開始,遍歷相鄰節點,更新索引堆中的權值
- visit(7)操作后索引堆狀態(0.19加入最小生成樹)
main_performance.cpp(測試算法性能)

1 #include <iostream> 2 #include <iomanip> 3 #include "SparseGraph.h" 4 #include "DenseGraph.h" 5 #include "ReadGraph.h" 6 #include "Component.h" 7 #include "Path.h" 8 #include "ShortestPath.h" 9 #include "LazyPrimMST.h" 10 #include "PrimMST.h" 11 #include <ctime> 12 13 using namespace std; 14 15 int main(){ 16 17 string filename1 = "testG1.txt"; 18 int V1 = 8; 19 20 string filename2 = "testG2.txt"; 21 int V2 = 250; 22 23 string filename3 = "testG3.txt"; 24 int V3 = 1000; 25 26 string filename4 = "testG4.txt"; 27 int V4 = 10000; 28 29 SparseGraph<double> g1 = SparseGraph<double>(V1, false); 30 ReadGraph<SparseGraph<double>,double> readGraph1(g1, filename1); 31 cout<<filename1<<" load successfully."<<endl; 32 33 SparseGraph<double> g2 = SparseGraph<double>(V2, false); 34 ReadGraph<SparseGraph<double>,double> readGraph2(g2, filename2); 35 cout<<filename2<<" load successfully."<<endl; 36 37 SparseGraph<double> g3 = SparseGraph<double>(V3, false); 38 ReadGraph<SparseGraph<double>,double> readGraph3(g3, filename3); 39 cout<<filename3<<" load successfully."<<endl; 40 41 SparseGraph<double> g4 = SparseGraph<double>(V4, false); 42 ReadGraph<SparseGraph<double>,double> readGraph4(g4, filename4); 43 cout<<filename4<<" load successfully."<<endl; 44 45 cout<<endl; 46 47 clock_t startTime, endTime; 48 49 // Test Lazy Prim MST 50 cout<<"Test Lazy Prim MST:"<<endl; 51 52 startTime = clock(); 53 LazyPrimMST<SparseGraph<double>, double> lazyPrimMST1(g1); 54 endTime = clock(); 55 cout<<"Test for G1: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 56 57 startTime = clock(); 58 LazyPrimMST<SparseGraph<double>, double> lazyPrimMST2(g2); 59 endTime = clock(); 60 cout<<"Test for G2: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 61 62 startTime = clock(); 63 LazyPrimMST<SparseGraph<double>, double> lazyPrimMST3(g3); 64 endTime = clock(); 65 cout<<"Test for G3: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 66 67 startTime = clock(); 68 LazyPrimMST<SparseGraph<double>, double> lazyPrimMST4(g4); 69 endTime = clock(); 70 cout<<"Test for G4: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 71 72 cout<<endl; 73 74 // Test Prim MST 75 cout<<"Test Prim MST:"<<endl; 76 77 startTime = clock(); 78 PrimMST<SparseGraph<double>, double> PrimMST1(g1); 79 endTime = clock(); 80 cout<<"Test for G1: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 81 82 startTime = clock(); 83 PrimMST<SparseGraph<double>, double> PrimMST2(g2); 84 endTime = clock(); 85 cout<<"Test for G2: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 86 87 startTime = clock(); 88 PrimMST<SparseGraph<double>, double> PrimMST3(g3); 89 endTime = clock(); 90 cout<<"Test for G3: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 91 92 startTime = clock(); 93 PrimMST<SparseGraph<double>, double> PrimMST4(g4); 94 endTime = clock(); 95 cout<<"Test for G4: "<<(double)(endTime-startTime)/CLOCKS_PER_SEC<<" s."<<endl; 96 97 cout<<endl; 98 99 return 0; 100 }
- Kruskal:MinHeap+Union Find實現,復雜度O(ElogE)
- 先把邊按權值排序,把權最小的邊加入最小生成樹,並判斷是否生成環,直到得到V-1條邊構成的生成樹
- 如果橫切邊有相等的邊:算法依然成立,任選一個邊,圖存在多個最小生成樹
- Vyssotsky's Algorithm:將邊逐漸地添加到生成樹中,一旦形成環,刪除環中權值最大的邊(沒有好的數據結構支撐)
KruskalMST.h

1 #include <iostream> 2 #include <vector> 3 #include "MinHeap.h" 4 #include "UF.h" 5 #include "Edge.h" 6 7 using namespace std; 8 9 template <typename Graph, typename Weight> 10 class KruskalMST{ 11 12 private: 13 vector<Edge<Weight>> mst; // MST的所有邊 14 Weight mstWeight; // MST的權值 15 public: 16 KruskalMST(Graph &graph){ 17 MinHeap<Edge<Weight>> pq( graph.E() ); 18 // 將所有邊放進最小堆中,完成排序 19 for( int i = 0 ; i < graph.V() ; i ++ ){ 20 typename Graph::adjIterator adj(graph,i); 21 for( Edge<Weight> *e = adj.begin() ; !adj.end() ; e = adj.next() ){ 22 // 1-2 2-1 放一個 23 if( e->v() < e->w() ) 24 pq.insert(*e); 25 } 26 } 27 // 創建並查集查看已訪問節點的連通情況 28 UnionFind uf(graph.V()); 29 while( !pq.isEmpty() && mst.size() < graph.V() - 1 ){ 30 Edge<Weight> e = pq.extractMin(); 31 // 是否成環 32 if( uf.isConnected( e.v() , e.w() )) 33 continue; 34 mst.push_back( e ); 35 uf.unionElements( e.v() , e.w() ); 36 } 37 38 mstWeight = mst[0].wt(); 39 for( int i = 1 ; i < mst.size() ; i ++ ) 40 mstWeight += mst[i].wt(); 41 } 42 ~KruskalMST(){ 43 44 } 45 // 返回MST的所有邊 46 vector<Edge<Weight>> mstEdge(){ 47 return mst; 48 } 49 // 返回MST的權值 50 Weight result(){ 51 return mstWeight; 52 } 53 };
- 單源最短路徑:最短路徑樹(所有頂點距離起始頂點權值最小)
- 廣度優先遍歷求出了無向圖中的單源最短路徑
- 松弛操作(Relaxation):找到一條經過更多頂點但總權值更小的路徑。是最短路徑求解的核心操作
- dijkstra:IndexMinHeap實現,復雜度O(ElogV)。可解決有/無向圖的單源最短路徑問題(無向圖相當於每條邊保存方向相反的兩條邊),要求圖中不能有負權邊
- 訪問距上個點最短的點,遍歷鄰邊,Relaxation,更新IndexMinHeap
main.cpp(測試dijkstra算法)

1 #include <iostream> 2 #include "SparseGraph.h" 3 #include "DenseGraph.h" 4 #include "ReadGraph.h" 5 #include "Dijkstra.h" 6 7 using namespace std; 8 9 int main(){ 10 11 string filename = "testG1.txt"; 12 int V = 5; 13 SparseGraph<int> g = SparseGraph<int>(V, true); 14 ReadGraph<SparseGraph<int>,int> readGraph(g, filename); 15 16 cout<<"Test Dijkstra:"<<endl<<endl ; 17 Dijkstra<SparseGraph<int>, int> dij(g,0); 18 for( int i = 0 ; i < V ; i ++ ){ 19 if(dij.hasPathTo(i)){ 20 cout<<"Shortest Path to "<<i<<" : "<<dij.shortestPathTo(i)<<endl; 21 dij.showPath(i); 22 } 23 else 24 cout<<"No Path to "<<i<<endl; 25 cout<<"-----------"<<endl; 26 } 27 return 0; 28 }
dijkstra.h

1 #include <iostream> 2 #include <vector> 3 #include <stack> 4 #include "Edge.h" 5 #include "IndexMinHeap.h" 6 7 using namespace std; 8 9 template<typename Graph, typename Weight> 10 class Dijkstra{ 11 private: 12 Graph &G; 13 int s; // 起始點 14 Weight *distTo; // distTo[i]記錄從s到i的最短路徑長度 15 bool *marked; // marked[i]記錄節點i是否被訪問 16 vector<Edge<Weight>*> from; // from[i]記錄到達i的邊是哪條 17 // 用於恢復最短路徑 18 // 使用*便於初始化賦空值 19 20 public: 21 Dijkstra(Graph &graph, int s):G(graph){ 22 assert( s >= 0 && s < G.V() ); 23 this->s = s; 24 distTo = new Weight[G.V()]; 25 marked = new bool[G.V()]; 26 for( int i = 0 ; i < G.V() ; i ++ ){ 27 // 初始化為默認值 28 distTo[i] = Weight(); 29 marked[i] = false; 30 from.push_back(NULL); 31 } 32 // 索引堆記錄當前找到的到達每個頂點的最短距離 33 IndexMinHeap<Weight> ipq(G.V()); 34 // Dijkstra 35 // 初始化起始點s 36 distTo[s] = Weight(); 37 from[s] = new Edge<Weight>(s, s, Weight()); 38 ipq.insert(s, distTo[s] ); 39 marked[s] = true; 40 ipq.insert( s , distTo[s] ); 41 while( !ipq.isEmpty() ){ 42 int v = ipq.extractMinIndex(); 43 // distTo[v]是s到v的最短距離 44 marked[v] = true; 45 // 松弛操作 46 // 訪問v的所有鄰邊 47 typename Graph::adjIterator adj(G, v); 48 for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ){ 49 int w = e->other(v); 50 // 若s到w的最短路徑還未找到 51 if( !marked[w] ){ 52 // 若w以前未訪問過 53 // 或訪問過,但通過當前v點到w點距離更短,則更新 54 if( from[w] == NULL || distTo[v] + e->wt() < distTo[w] ){ 55 distTo[w] = distTo[v] + e->wt(); 56 from[w] = e; 57 if( ipq.contain(w) ) 58 ipq.change(w, distTo[w]); 59 else 60 ipq.insert(w, distTo[w]); 61 } 62 } 63 } 64 } 65 } 66 67 ~Dijkstra(){ 68 delete[] distTo; 69 delete[] marked; 70 delete from[0]; 71 } 72 73 // 返回從s到w的最短路徑長度 74 Weight shortestPathTo( int w ){ 75 assert( w >= 0 && w < G.V() ); 76 assert( hasPathTo(w) ); 77 return distTo[w]; 78 } 79 // 判斷從s到w是否連通 80 bool hasPathTo( int w ){ 81 assert( w >= 0 && w < G.V() ); 82 return marked[w]; 83 } 84 // 尋找從s到w的最短路徑,將整個路徑經過的邊放入vec 85 void shortestPath( int w, vector<Edge<Weight>> &vec){ 86 assert( w >= 0 && w < G.V() ); 87 assert( hasPathTo(w) ); 88 // 通過from數組逆向查找從s到w的路徑,存入棧中 89 stack<Edge<Weight>*> s; 90 Edge<Weight> *e = from[w]; 91 while( e->v() != e->w() ){ 92 s.push(e); 93 e = from[e->v()]; 94 } 95 // 從棧取出元素,獲得順序的從s到w的路徑 96 while( !s.empty() ){ 97 e = s.top(); 98 vec.push_back( *e ); 99 s.pop(); 100 } 101 } 102 // 打印從s到w的路徑 103 void showPath( int w ){ 104 assert( w >= 0 && w < G.V() ); 105 vector<Edge<Weight>> vec; 106 shortestPath(w, vec); 107 for( int i = 0 ; i < vec.size() ; i ++ ){ 108 cout << vec[i].v() << " -> "; 109 if( i == vec.size()-1 ) 110 cout << vec[i].w() << endl; 111 } 112 } 113 };
- 負權邊問題:本質上仍是松弛操作
- 如果一個圖有負權環,則不存在最短路徑,如下圖的1-2
- Bellman-Ford單源最短路徑算法:可判斷圖中是否有負權環,運行前不需檢測。復雜度O(EV)
- 若一個圖沒有負權環,則從一個點到另一點的最短路徑,最多經過所有的V個頂點,有V-1條邊,否則存在頂點經過了兩次,既存在負權環
- 對一個點的一次松弛操作,就是找到經過這個點的另外一條路徑,多一條邊,權值更小;若圖沒有負權環,從一個點到另外一點的最短路徑,最多經過所有的V個頂點,有V-1條邊;對所有點進行V-1次松弛操作,即可找到從原點到其他所有點的最短路徑;如果還可以繼續松弛,則原圖中有負權環
- 適用於有向圖,若是無向圖,負邊自身構成負權環
main.cpp(測試Bellman-Ford)

1 #include <iostream> 2 #include "SparseGraph.h" 3 #include "DenseGraph.h" 4 #include "ReadGraph.h" 5 #include "BellmanFord1.h" 6 7 using namespace std; 8 9 // 測試Bellman-Ford算法 10 int main() { 11 string filename = "testG2.txt"; 12 //string filename = "testG_negative_circle.txt"; 13 int V = 5; 14 SparseGraph<int> g = SparseGraph<int>(V, true); 15 ReadGraph<SparseGraph<int>, int> readGraph(g, filename); 16 cout<<"Test Bellman-Ford:"<<endl<<endl; 17 int s = 0; 18 BellmanFord<SparseGraph<int>, int> bellmanFord(g, s); 19 if( bellmanFord.negativeCycle() ) 20 cout<<"The graph contain negative cycle!"<<endl; 21 else 22 for( int i = 0 ; i < V ; i ++ ) { 23 if(i == s) 24 continue; 25 if (bellmanFord.hasPathTo(i)) { 26 cout << "Shortest Path to " << i << " : " << bellmanFord.shortestPathTo(i) << endl; 27 bellmanFord.showPath(i); 28 } 29 else 30 cout << "No Path to " << i << endl; 31 cout << "----------" << endl; 32 } 33 return 0; 34 }
BellmanFord.h

1 #include <stack> 2 #include <vector> 3 #include "Edge.h" 4 5 using namespace std; 6 7 template <typename Graph, typename Weight> 8 class BellmanFord{ 9 private: 10 Graph &G; 11 int s; // 起始點 12 Weight* distTo; // distTo[i]為從起始點s到i的最短路徑長度 13 vector<Edge<Weight>*> from; // from[i]為最短路徑中,到達i的是哪條邊 14 bool hasNegativeCycle; // 是否有負權環 15 // 判斷是否有負權環 16 bool detectNegativeCycle(){ 17 for( int i = 0 ; i < G.V() ; i ++ ){ 18 typename Graph::adjIterator adj(G,i); 19 for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ) 20 if( from[e->v()] && distTo[e->v()] + e->wt() < distTo[e->w()] ) 21 return true; 22 } 23 return false; 24 } 25 26 public: 27 BellmanFord(Graph &graph, int s):G(graph){ 28 this->s = s; 29 distTo = new Weight[G.V()]; 30 // 初始化,所有節點s都不可達 31 for( int i = 0 ; i < G.V() ; i ++ ) 32 from.push_back(NULL); 33 // 設置distTo[s] = 0,from[s]不為NULL,表示初始點s可達且距離為0 34 distTo[s] = Weight(); 35 from[s] = new Edge<Weight>(s, s, Weight()); 36 // Bellman-Ford 37 // 進行V-1輪松弛操作,每次求出從起點到其余所有點,最多用pass步可到達的最短距離 38 for( int pass = 1 ; pass < G.V() ; pass ++ ){ 39 // 每次循環中對所有邊進行一遍松弛操作 40 // 先遍歷所有頂點,然后遍歷和所有頂點相鄰的所有邊 41 for( int i = 0 ; i < G.V() ; i ++ ){ 42 typename Graph::adjIterator adj(G,i); 43 for( Edge<Weight>* e = adj.begin() ; !adj.end() ; e = adj.next() ) 44 // e為i的鄰邊 45 // 對每個邊首先判斷e->v()是否到過 46 // 如果e->w()以前沒有到達過,則更新distTo[e->w()] 47 // 或雖然e->w()以前到達過,但通過這個e可獲得一個更短的距離 48 // 即進行一次松弛操作,更新distTo[e->w()] 49 if( from[e->v()] && (!from[e->w()] || distTo[e->v()] + e->wt() < distTo[e->w()])){ 50 distTo[e->w()] = distTo[e->v()] + e->wt(); 51 // 通過e到達e->w() 52 from[e->w()] = e; 53 } 54 } 55 } 56 hasNegativeCycle = detectNegativeCycle(); 57 } 58 ~BellmanFord(){ 59 delete[] distTo; 60 delete from[s]; 61 } 62 // 返回圖中是否有負環 63 bool negativeCycle(){ 64 return hasNegativeCycle; 65 } 66 // 返回s到w的最短路徑長度 67 Weight shortestPathTo( int w ){ 68 assert( w >= 0 && w < G.V() ); 69 assert( !hasNegativeCycle ); 70 assert( hasPathTo(w) ); 71 return distTo[w]; 72 } 73 // 判斷從s到w是否連通 74 bool hasPathTo( int w ){ 75 assert( w >= 0 && w < G.V() ); 76 return from[w] != NULL; 77 } 78 // 從s到w的最短路徑,將經過的邊放在vec中 79 void shortestPath( int w, vector<Edge<Weight>> &vec ){ 80 // w不越界 81 assert( w >= 0 && w < G.V() ); 82 // 無負權環 83 assert( !hasNegativeCycle ); 84 assert( hasPathTo(w) ); 85 // 通過from逆向查找從s到w的路徑,存在棧中 86 stack<Edge<Weight>*> s; 87 Edge<Weight> *e = from[w]; 88 while( e->v() != this->s ){ 89 s.push(e); 90 e = from[e->v()]; 91 } 92 s.push(e); 93 // 從棧中依次取出元素,獲得順序的從s到w的路徑 94 while( !s.empty() ){ 95 e = s.top(); 96 vec.push_back( *e ); 97 s.pop(); 98 } 99 } 100 // 打印從s到w的路徑 101 void showPath(int w){ 102 assert( w >= 0 && w < G.V() ); 103 assert( !hasNegativeCycle); 104 assert( hasPathTo(w) ); 105 106 vector<Edge<Weight>> vec; 107 shortestPath(w,vec); 108 for( int i = 0 ; i < vec.size() ; i ++ ){ 109 cout << vec[i].v()<<" -> "; 110 if( i == vec.size()-1 ) 111 cout << vec[i].w() << endl; 112 } 113 } 114 };
- 單源最短路徑算法對比(指定起點s)
dijkstra 無負權邊 有向無向圖均可 O(ElogV)
Bellman-Ford 無負權環 有向圖 O(VE)
利用拓撲排序 有向無環圖 有向圖 O(V+E)
- 所有對最短路徑算法(任意兩點a、b)
- Floyed算法,處理無負權環的圖,復雜度O(V^3)
- 最長路徑算法
- 不能有正權環
- 無權圖的最長路徑問題是指數級難度
- 有權圖,不能使用Dijkstra求最長路徑
- 可使用Bellman-Ford