[算法] 带权图

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 }
View Code

技术图片

技术图片

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
View Code

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     };
View Code
  • 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 }
View Code

技术图片

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 };
View Code

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 }; 
View Code
  • 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 }
View Code

 技术图片

  • Kruskal:先把边按权值排序,把权最小的边加入最小生成树,并判断是否生成环,直到得到V-1条边构成的生成树
  • 使用Union Find判断环

以上是关于[算法] 带权图的主要内容,如果未能解决你的问题,请参考以下文章

java数据结构----带权图

无向带权图的最小生成树算法——Prim及Kruskal算法思路

[算法] 带权图

搜索带权图最短路径的Dijkstra算法

最短路径问题之Dijkstra算法(C语言)

学习数据结构笔记(14) --- [图]