[关闭]
@CrazyHenry 2018-03-15T12:12:23.000000Z 字数 10616 阅读 958

0.x 14.图论基础-表示和遍历(无权图)

dddd数据结构课本


1.邻接表和邻接矩阵

邻接矩阵

  1. #include <iostream>
  2. #include <vector>
  3. #include <cassert>
  4. using namespace std;
  5. // 稠密图 - 邻接矩阵
  6. class DenseGraph{
  7. private:
  8. int n, m; // 节点数和边数
  9. bool directed; // 是否为有向图
  10. vector<vector<bool>> g; // 图的具体数据
  11. public:
  12. // 构造函数
  13. DenseGraph( int n , bool directed )
  14. : g(n, vector<bool>(n, false)),n(n),m(0),directed(directed){}
  15. ~DenseGraph(){}
  16. int V(){ return n;} // 返回节点个数
  17. int E(){ return m;} // 返回边的个数
  18. // 向图中添加一个边
  19. void addEdge( int v , int w ){
  20. assert( v >= 0 && v < n );
  21. assert( w >= 0 && w < n );
  22. if(v != w)//自环边避免
  23. {
  24. if( hasEdge( v , w ) )
  25. return; //去除平行边
  26. g[v][w] = true;
  27. if( !directed )
  28. g[w][v] = true;
  29. ++m;//无向图中有连接的边只算一条
  30. }
  31. }
  32. // 验证图中是否有从v到w的边
  33. bool hasEdge( int v , int w ){ //O(1)来判断是否有连接
  34. return g[v][w];
  35. }
  36. };

邻接表

  1. #include <iostream>
  2. #include <vector>
  3. #include <cassert>
  4. using namespace std;
  5. // 稀疏图 - 邻接表
  6. class SparseGraph{
  7. private:
  8. int n, m; // 节点数和边数
  9. bool directed; // 是否为有向图
  10. vector<vector<int>> g; // 图的具体数据
  11. public:
  12. // 构造函数
  13. SparseGraph( int n , bool directed )
  14. : n(n),m(0),directed(directed),g(n, vector<int>()){}
  15. ~SparseGraph(){}
  16. int V(){ return n;} // 返回节点个数
  17. int E(){ return m;} // 返回边的个数
  18. // 向图中添加一个边
  19. void addEdge( int v, int w ){
  20. assert( v >= 0 && v < n );
  21. assert( w >= 0 && w < n );
  22. if(v != w)
  23. {
  24. g[v].push_back(w);
  25. if(!directed )
  26. g[w].push_back(v);
  27. ++m;
  28. }
  29. }
  30. // 验证图中是否有从v到w的边
  31. bool hasEdge( int v , int w ){ //O(n)的复杂度,因此一般不判断平行边
  32. assert( v >= 0 && v < n );
  33. assert( w >= 0 && w < n );
  34. for( int i = 0 ; i != g[v].size() ; ++i )
  35. if( g[v][i] == w )
  36. return true;
  37. return false;
  38. }
  39. };

2.打印一张图

邻接表

  1. #include <iostream>
  2. #include <vector>
  3. #include <cassert>
  4. using namespace std;
  5. // 稀疏图 - 邻接表
  6. class SparseGraph{
  7. private:
  8. int n, m; // 节点数和边数
  9. bool directed; // 是否为有向图
  10. vector<vector<int>> g; // 图的具体数据
  11. public:
  12. // 构造函数
  13. SparseGraph( int n , bool directed )
  14. : n(n),m(0),directed(directed),g(n, vector<int>()){}
  15. ~SparseGraph(){}
  16. int V(){ return n;} // 返回节点个数
  17. int E(){ return m;} // 返回边的个数
  18. // 向图中添加一个边
  19. void addEdge( int v, int w ){
  20. assert( v >= 0 && v < n );
  21. assert( w >= 0 && w < n );
  22. if(v != w)
  23. {
  24. g[v].push_back(w);
  25. if(!directed )
  26. g[w].push_back(v);
  27. ++m;
  28. }
  29. }
  30. // 验证图中是否有从v到w的边
  31. bool hasEdge( int v , int w ){ //O(n)的复杂度,因此一般不判断平行边
  32. assert( v >= 0 && v < n );
  33. assert( w >= 0 && w < n );
  34. for( int i = 0 ; i != g[v].size() ; ++i )
  35. if( g[v][i] == w )
  36. return true;
  37. return false;
  38. }
  39. // 邻边迭代器, 传入一个图和一个顶点,
  40. // 迭代在这个图中和这个顶点向连的所有顶点
  41. class adjIterator{
  42. private:
  43. SparseGraph &G; // 图G的引用
  44. int v;
  45. int index;
  46. public:
  47. // 构造函数
  48. adjIterator(SparseGraph &graph, int v): G(graph),v(v),index(0){}
  49. ~adjIterator(){}
  50. // 返回图G中与顶点v相连接的第一个顶点
  51. int begin(){
  52. index = 0;
  53. if( G.g[v].size() )
  54. return G.g[v][index];
  55. // 若没有顶点和v相连接, 则返回-1
  56. return -1;
  57. }
  58. // 返回图G中与顶点v相连接的下一个顶点
  59. int next(){
  60. ++index;
  61. if( index < G.g[v].size() )
  62. return G.g[v][index];
  63. // 若没有顶点和v相连接, 则返回-1
  64. return -1;
  65. }
  66. // 查看是否已经迭代完了图G中与顶点v相连接的所有顶点
  67. bool end(){
  68. return index >= G.g[v].size();
  69. }
  70. };
  71. };
  1. //main.cpp
  2. #include <iostream>
  3. #include "SparseGraph.h"
  4. #include "DenseGraph.h"
  5. using namespace std;
  6. int main() {
  7. int N = 20;
  8. int M = 100;
  9. srand( time(NULL) );
  10. // Sparse Graph
  11. SparseGraph g1(N , false);
  12. for( int i = 0 ; i < M ; i ++ ){
  13. int a = rand()%N;
  14. int b = rand()%N;
  15. g1.addEdge( a , b );
  16. }
  17. // O(E)
  18. for( int v = 0 ; v < N ; v ++ ){
  19. cout<<v<<" : ";
  20. SparseGraph::adjIterator adj( g1 , v );
  21. for( int w = adj.begin() ; !adj.end() ; w = adj.next() )
  22. cout<<w<<" ";
  23. cout<<endl;
  24. }
  25. cout<<endl;
  26. // Dense Graph
  27. DenseGraph g2(N , false);
  28. for( int i = 0 ; i < M ; i ++ ){
  29. int a = rand()%N;
  30. int b = rand()%N;
  31. g2.addEdge( a , b );
  32. }
  33. // O(V^2)
  34. for( int v = 0 ; v < N ; v ++ ){
  35. cout<<v<<" : ";
  36. DenseGraph::adjIterator adj( g2 , v );
  37. for( int w = adj.begin() ; !adj.end() ; w = adj.next() )
  38. cout<<w<<" ";
  39. cout<<endl;
  40. }
  41. return 0;
  42. }

邻接矩阵

  1. // 邻边迭代器, 传入一个图和一个顶点,
  2. // 迭代在这个图中和这个顶点向连的所有顶点
  3. class adjIterator{
  4. private:
  5. DenseGraph &G; // 图G的引用
  6. int v;
  7. int index;
  8. public:
  9. // 构造函数
  10. adjIterator(DenseGraph &graph, int v): G(graph),v(v),index(-1){}
  11. // 索引从-1开始, 因为每次遍历都需要调用一次next()
  12. ~adjIterator(){}
  13. // 返回图G中与顶点v相连接的第一个顶点
  14. int begin(){
  15. // 索引从-1开始, 因为每次遍历都需要调用一次next()
  16. index = -1;
  17. return next();
  18. }
  19. // 返回图G中与顶点v相连接的下一个顶点
  20. int next(){
  21. // 从当前index开始向后搜索, 直到找到一个g[v][index]为true
  22. for( index += 1 ; index < G.V() ; ++index )
  23. if( G.g[v][index] )
  24. return index;
  25. // 若没有顶点和v相连接, 则返回-1
  26. return -1;
  27. }
  28. // 查看是否已经迭代完了图G中与顶点v相连接的所有顶点
  29. bool end(){
  30. return index >= G.V();
  31. }
  32. };
  1. //main.cpp
  2. #include <iostream>
  3. #include "SparseGraph.h"
  4. #include "DenseGraph.h"
  5. using namespace std;
  6. int main() {
  7. int N = 20;
  8. int M = 100;
  9. srand( time(NULL) );
  10. // Sparse Graph
  11. SparseGraph g1(N , false);
  12. for( int i = 0 ; i < M ; i ++ ){
  13. int a = rand()%N;
  14. int b = rand()%N;
  15. g1.addEdge( a , b );
  16. }
  17. // O(E)
  18. for( int v = 0 ; v < N ; v ++ ){
  19. cout<<v<<" : ";
  20. SparseGraph::adjIterator adj( g1 , v );
  21. for( int w = adj.begin() ; !adj.end() ; w = adj.next() )
  22. cout<<w<<" ";
  23. cout<<endl;
  24. }
  25. cout<<endl;
  26. // Dense Graph
  27. DenseGraph g2(N , false);
  28. for( int i = 0 ; i < M ; i ++ ){
  29. int a = rand()%N;
  30. int b = rand()%N;
  31. g2.addEdge( a , b );
  32. }
  33. // O(V^2)
  34. for( int v = 0 ; v < N ; v ++ ){
  35. cout<<v<<" : ";
  36. DenseGraph::adjIterator adj( g2 , v );
  37. for( int w = adj.begin() ; !adj.end() ; w = adj.next() )
  38. cout<<w<<" ";
  39. cout<<endl;
  40. }
  41. return 0;
  42. }

3.读取文件建立图,打印矩阵和邻接表

  1. #include <iostream>
  2. #include <string>
  3. #include <fstream>
  4. #include <sstream>
  5. #include <cassert>
  6. using namespace std;
  7. // 读取图算法
  8. template <typename Graph> //表示SparseGraph或者DenseGraph
  9. class ReadGraph{
  10. public:
  11. // 从文件filename中读取图的信息, 存储进图graph中
  12. ReadGraph(Graph &graph, const string &filename){
  13. ifstream file(filename);
  14. string line;
  15. int V, E;
  16. assert( file.is_open() );
  17. // 第一行读取图中的节点个数和边的个数
  18. assert( getline(file, line) );
  19. stringstream ss(line);
  20. ss>>V>>E;
  21. assert( V == graph.V() ); //建立Graph对象时给定了V,判断是否一致
  22. // 读取每一条边的信息
  23. for( int i = 0 ; i < E ; i ++ ){
  24. assert( getline(file, line) );
  25. stringstream ss(line);
  26. int a,b;
  27. ss>>a>>b;
  28. assert( a >= 0 && a < V );
  29. assert( b >= 0 && b < V );
  30. graph.addEdge( a , b );
  31. }
  32. }
  33. };
  1. //打印DenseGraph
  2. void show(){
  3. for( int i = 0 ; i < n ; i ++ ){
  4. for( int j = 0 ; j < n ; j ++ )
  5. cout<<g[i][j]<<"\t";
  6. cout<<endl;
  7. }
  8. }
  1. //打印SparseGraph
  2. // 显示图的信息
  3. void show(){
  4. for( int i = 0 ; i < n ; i ++ ){
  5. cout<<"vertex "<<i<<":\t";
  6. for( int j = 0 ; j < g[i].size() ; j ++ )
  7. cout<<g[i][j]<<"\t";
  8. cout<<endl;
  9. }
  10. }
  1. //main.cpp
  2. #include <iostream>
  3. #include "SparseGraph.h"
  4. #include "DenseGraph.h"
  5. #include "ReadGraph.h"
  6. using namespace std;
  7. // 测试通过文件读取图的信息
  8. int main() {
  9. // 使用两种图的存储方式读取testG1.txt文件
  10. string filename = "testG1.txt";
  11. SparseGraph g1( 13 , false );
  12. ReadGraph<SparseGraph> readGraph1( g1 , filename );
  13. cout<<"test G1 in Sparse Graph:" << endl;
  14. g1.show();
  15. cout<<endl;
  16. DenseGraph g2( 13 , false );
  17. ReadGraph<DenseGraph> readGraph2( g2 , filename );
  18. cout<<"test G1 in Dense Graph:" << endl;
  19. g2.show();
  20. cout<<endl;
  21. // 使用两种图的存储方式读取testG2.txt文件
  22. filename = "testG2.txt";
  23. SparseGraph g3( 6 , false );
  24. ReadGraph<SparseGraph> readGraph3( g3 , filename );
  25. cout<<"test G2 in Sparse Graph:" << endl;
  26. g3.show();
  27. cout<<endl;
  28. DenseGraph g4( 6 , false );
  29. ReadGraph<DenseGraph> readGraph4( g4 , filename );
  30. cout<<"test G2 in Dense Graph:" << endl;
  31. g4.show();
  32. return 0;
  33. }

4.图的深度优先遍历与连通分量个数+判断顶点的连通性

  1. #include <iostream>
  2. #include <cassert>
  3. using namespace std;
  4. // 求无权图的联通分量
  5. template <typename Graph>
  6. class Component{
  7. private:
  8. Graph &G; // 图的引用
  9. bool *visited; // 记录dfs的过程中节点是否被访问
  10. int ccount; // 记录联通分量个数
  11. int *id; // 每个节点所对应的联通分量标记
  12. // 图的深度优先遍历
  13. void dfs( int v ){
  14. visited[v] = true;
  15. id[v] = ccount;
  16. typename Graph::adjIterator adj(G, v);
  17. for( int i = adj.begin() ; !adj.end() ; i = adj.next() ){
  18. if( !visited[i] )
  19. dfs(i);//i为顶点的下标
  20. }
  21. }
  22. public:
  23. // 构造函数, 求出无权图的联通分量
  24. Component(Graph &graph): G(graph){
  25. // 算法初始化
  26. visited = new bool[G.V()]{};
  27. id = new int[G.V()]{};
  28. ccount = 0;
  29. for( int i = 0 ; i < G.V() ; ++i ){
  30. visited[i] = false;
  31. id[i] = -1;
  32. }
  33. // 求图的联通分量
  34. for( int i = 0 ; i < G.V() ; ++i )
  35. if( !visited[i] ){
  36. dfs(i);
  37. ++ccount;
  38. }
  39. }
  40. // 析构函数
  41. ~Component(){
  42. delete[] visited;
  43. delete[] id;
  44. }
  45. // 返回图的联通分量个数
  46. int count(){
  47. return ccount;
  48. }
  49. // 查询点v和点w是否联通
  50. bool isConnected( int v , int w ){
  51. assert( v >= 0 && v < G.V() );
  52. assert( w >= 0 && w < G.V() );
  53. return id[v] == id[w];
  54. }
  55. };
  56. #include <iostream>
  57. #include "SparseGraph.h"
  58. #include "DenseGraph.h"
  59. #include "ReadGraph.h"
  60. #include "Components.h"
  61. using namespace std;
  62. // 测试图的联通分量算法
  63. int main() {
  64. // TestG1.txt
  65. string filename1 = "testG1.txt";
  66. SparseGraph g1 = SparseGraph(13, false);
  67. ReadGraph<SparseGraph> readGraph1(g1, filename1);
  68. Component<SparseGraph> component1(g1);
  69. cout<<"TestG1.txt, Component Count: "<<component1.count()<<endl;
  70. cout<<endl;
  71. // TestG2.txt
  72. string filename2 = "testG2.txt";
  73. SparseGraph g2 = SparseGraph(7, false);
  74. ReadGraph<SparseGraph> readGraph2(g2, filename2);
  75. Component<SparseGraph> component2(g2);
  76. cout<<"TestG2.txt, Component Count: "<<component2.count()<<endl;
  77. return 0;
  78. }

5.深度优先遍历与寻路

  1. #include <vector>
  2. #include <stack>
  3. #include <iostream>
  4. #include <cassert>
  5. using namespace std;
  6. // 路径查询
  7. template <typename Graph>
  8. class Path{
  9. private:
  10. Graph &G; // 图的引用
  11. int s; // 起始点
  12. bool* visited; // 记录dfs的过程中节点是否被访问
  13. int * from; // 记录路径, from[i]表示查找的路径上i的上一个节点
  14. // 图的深度优先遍历
  15. void dfs( int v ){
  16. visited[v] = true;
  17. typename Graph::adjIterator adj(G, v);
  18. for( int i = adj.begin() ; !adj.end() ; i = adj.next() ){
  19. if( !visited[i] ){
  20. from[i] = v;
  21. dfs(i);
  22. }
  23. }
  24. }
  25. public:
  26. // 构造函数, 寻路算法, 寻找图graph从s点到其他点的路径
  27. Path(Graph &graph, int s):G(graph){
  28. // 算法初始化
  29. assert( s >= 0 && s < G.V() );
  30. visited = new bool[G.V()];
  31. from = new int[G.V()];
  32. for( int i = 0 ; i < G.V() ; i ++ ){
  33. visited[i] = false;
  34. from[i] = -1;
  35. }
  36. this->s = s;
  37. // 寻路算法
  38. dfs(s);
  39. }
  40. // 析构函数
  41. ~Path(){
  42. delete [] visited;
  43. delete [] from;
  44. }
  45. // 查询从s点到w点是否有路径
  46. bool hasPath(int w){
  47. assert( w >= 0 && w < G.V() );
  48. return visited[w];
  49. }
  50. // 查询从s点到w点的路径, 存放在vec中
  51. void path(int w, vector<int> &vec){
  52. assert( hasPath(w) );
  53. stack<int> s;
  54. // 通过from数组逆向查找到从s到w的路径, 存放到栈中
  55. int p = w;
  56. while( p != -1 ){
  57. s.push(p);
  58. p = from[p];
  59. }
  60. // 从栈中依次取出元素, 获得顺序的从s到w的路径
  61. vec.clear();
  62. while( !s.empty() ){
  63. vec.push_back( s.top() );
  64. s.pop();
  65. }
  66. }
  67. // 打印出从s点到w点的路径
  68. void showPath(int w){
  69. assert( hasPath(w) );
  70. vector<int> vec;
  71. path( w , vec );
  72. for( int i = 0 ; i < vec.size() ; i ++ ){
  73. cout<<vec[i];
  74. if( i == vec.size() - 1 )
  75. cout<<endl;
  76. else
  77. cout<<" -> ";
  78. }
  79. }
  80. };
  81. //main
  82. #include <iostream>
  83. #include "SparseGraph.h"
  84. #include "DenseGraph.h"
  85. #include "ReadGraph.h"
  86. #include "Path.h"
  87. using namespace std;
  88. // 测试寻路算法
  89. int main() {
  90. string filename = "testG.txt";
  91. SparseGraph g = SparseGraph(7, false);
  92. ReadGraph<SparseGraph> readGraph(g, filename);
  93. g.show();
  94. cout<<endl;
  95. Path<SparseGraph> path(g,0);
  96. cout<<"Path from 0 to 6 : " << endl;
  97. path.showPath(6);
  98. return 0;
  99. }

6.广度优先遍历与最短路径

  1. // 寻找无权图的最短路径
  2. template <typename Graph>
  3. class ShortestPath{
  4. private:
  5. Graph &G; // 图的引用
  6. int s; // 起始点
  7. bool *visited; // 记录dfs的过程中节点是否被访问
  8. int *from; // 记录路径, from[i]表示查找的路径上i的上一个节点
  9. int *ord; // 记录路径中节点的次序。ord[i]表示i节点在路径中的次序。
  10. public:
  11. // 构造函数, 寻找无权图graph从s点到其他点的最短路径
  12. ShortestPath(Graph &graph, int s):G(graph){
  13. // 算法初始化
  14. assert( s >= 0 && s < graph.V() );
  15. visited = new bool[graph.V()];
  16. from = new int[graph.V()];
  17. ord = new int[graph.V()];
  18. for( int i = 0 ; i < graph.V() ; i ++ ){
  19. visited[i] = false;
  20. from[i] = -1;
  21. ord[i] = -1;
  22. }
  23. this->s = s;
  24. // 无向图最短路径算法, 从s开始广度优先遍历整张图
  25. queue<int> q;
  26. q.push( s );
  27. visited[s] = true;
  28. ord[s] = 0;
  29. while( !q.empty() ){
  30. int v = q.front();
  31. q.pop();
  32. typename Graph::adjIterator adj(G, v);
  33. for( int i = adj.begin() ; !adj.end() ; i = adj.next() )
  34. if( !visited[i] ){
  35. q.push(i);
  36. visited[i] = true;
  37. from[i] = v;
  38. ord[i] = ord[v] + 1;
  39. }
  40. }
  41. }
  42. // 析构函数
  43. ~ShortestPath(){
  44. delete [] visited;
  45. delete [] from;
  46. delete [] ord;
  47. }
  48. // 查询从s点到w点是否有路径
  49. bool hasPath(int w){
  50. assert( w >= 0 && w < G.V() );
  51. return visited[w];
  52. }
  53. // 查询从s点到w点的路径, 存放在vec中
  54. void path(int w, vector<int> &vec){
  55. assert( w >= 0 && w < G.V() );
  56. stack<int> s;
  57. // 通过from数组逆向查找到从s到w的路径, 存放到栈中
  58. int p = w;
  59. while( p != -1 ){
  60. s.push(p);
  61. p = from[p];
  62. }
  63. // 从栈中依次取出元素, 获得顺序的从s到w的路径
  64. vec.clear();
  65. while( !s.empty() ){
  66. vec.push_back( s.top() );
  67. s.pop();
  68. }
  69. }
  70. // 打印出从s点到w点的路径
  71. void showPath(int w){
  72. assert( w >= 0 && w < G.V() );
  73. vector<int> vec;
  74. path(w, vec);
  75. for( int i = 0 ; i < vec.size() ; i ++ ){
  76. cout<<vec[i];
  77. if( i == vec.size()-1 )
  78. cout<<endl;
  79. else
  80. cout<<" -> ";
  81. }
  82. }
  83. // 查看从s点到w点的最短路径长度
  84. int length(int w){
  85. assert( w >= 0 && w < G.V() );
  86. return ord[w];
  87. }
  88. };
  89. //main
  90. #include <iostream>
  91. #include "SparseGraph.h"
  92. #include "DenseGraph.h"
  93. #include "ReadGraph.h"
  94. #include "Path.h"
  95. #include "ShortestPath.h"
  96. using namespace std;
  97. // 测试无权图最短路径算法
  98. int main() {
  99. string filename = "testG.txt";
  100. SparseGraph g = SparseGraph(7, false);
  101. ReadGraph<SparseGraph> readGraph(g, filename);
  102. g.show();
  103. cout<<endl;
  104. // 比较使用深度优先遍历和广度优先遍历获得路径的不同
  105. // 广度优先遍历获得的是无权图的最短路径
  106. Path<SparseGraph> dfs(g,0);
  107. cout<<"DFS : ";
  108. dfs.showPath(6);
  109. ShortestPath<SparseGraph> bfs(g,0);
  110. cout<<"BFS : ";
  111. bfs.showPath(6);
  112. return 0;
  113. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注