[算法] 带权图
Posted cxc1357
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[算法] 带权图相关的知识,希望对你有一定的参考价值。
- 最小生成树(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:先把边按权值排序,把权最小的边加入最小生成树,并判断是否生成环,直到得到V-1条边构成的生成树
- 使用Union Find判断环
以上是关于[算法] 带权图的主要内容,如果未能解决你的问题,请参考以下文章