c++判断有向图是否有环 、环的个数以及环中元素



c++判断有向图是否有环 、环的个数以及环中元素。判断有向图是否有环有三种方法:拓扑排序、深度遍历+回溯、深度遍历 + 判断后退边

这里使用 拓扑排序 和 深度遍历 + 回溯判断是不是环。使用 深度遍历 + 判断后退边找出环个数 以及环中元素

1、拓扑排序

思想:找入度为0的顶点,输出顶点,删除出边。循环到无顶点输出。

若:输出所有顶点,则课拓扑排序,无环;反之,则不能拓扑排序,有环

使用:可以使用拓扑排序为有向无环图每一个结点进行编号,拓扑排序输出的顺序可以为编号顺序

源代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int MAX_Vertex_Num = 20;
  4. template<class VexType,class ArcType>
  5. class MGraph
  6. {
  7. public:
  8.     void CreateGraph();//创建图
  9.     int LocateVex(VexType v);//返回顶点v所在顶点向量中的位置(下标)
  10.     void CheckCircle();
  11. private:
  12.     VexType vexs[MAX_Vertex_Num];//顶点向量
  13.     ArcType arcs[MAX_Vertex_Num][MAX_Vertex_Num]; //这里把邻接矩阵类型用模板表示,主要是为了处理有权值的情况,比如:权值可以为小数(代价),也可以为整数
  14.     int vexnum;//顶点数
  15.     int arcnum;//边数
  16. private:
  17.     bool TopSort();
  18. };
  19. template<class VexType,class ArcType>
  20. void MGraph<VexType,ArcType>::CreateGraph()
  21. {
  22.     VexType first;
  23.     VexType Secend;
  24.     cout<<”请输入顶点数:”;
  25.     cin>>vexnum;
  26.     cout<<”请输入边数:”;
  27.     cin>>arcnum;
  28.     cout<<”请输入各个顶点值:”;
  29.     for (int i=0;i<vexnum;i++)
  30.     {
  31.         cin>>vexs[i];
  32.     }
  33.     //初始化邻接矩阵
  34.     for (int i=0;i<arcnum;i++)
  35.     {
  36.         for (int j=0;j<arcnum;j++)
  37.         {
  38.             arcs[i][j]=0;
  39.         }
  40.     }
  41.     cout<<”请输入边的信息:”<<endl;
  42.     for (int i=0;i<arcnum;i++)
  43.     {
  44.         cin>>first>>Secend;
  45.         //如果边有权值的话,则还应该输入权值
  46.         int x = LocateVex(first);
  47.         int y = LocateVex(Secend);
  48.         arcs[x][y]=1;//如果是有权的话,这里应该是arc[x][y]=权值
  49.     }
  50. }
  51. /*
  52. 参数:v:表示顶点向量中一个值
  53. 函数返回值:函数返回v在顶点向量中的下标
  54. */
  55. template<class VexType,class ArcType>
  56. int MGraph<VexType,ArcType>::LocateVex(VexType v)
  57. {
  58.     for (int i=0;i<vexnum;i++)
  59.     {
  60.         if (vexs[i]==v)
  61.         {
  62.             return i;
  63.         }
  64.     }
  65.     return -1;
  66. }
  67. /*
  68. 有向图可以拓扑排序的条件是:图中没有环。
  69. 具体方法:
  70. ⑴ 从图中选择一个入度为0的点加入拓扑序列。
  71. ⑵ 从图中删除该结点以及它的所有出边(即与之相邻点入度减1)。
  72. */
  73. template<class VexType,class ArcType>
  74. bool MGraph<VexType,ArcType>::TopSort()
  75. {
  76.     int count = 0;//拓扑排序输出顶点的个数
  77.     int top = -1;
  78.     int stack[MAX_Vertex_Num];
  79.     int indegree[MAX_Vertex_Num]={0};
  80.     //求各个顶点的入度–邻接矩阵要查询该元素的列(记录入度情况)–
  81.     //如果是邻接表,就是麻烦在这里,查询结点入度很不方便
  82.     for (int i=0;i<vexnum;i++)
  83.     {
  84.         int num=0;
  85.         for (int j=0;j<vexnum;j++)
  86.         {
  87.             if (arcs[j][i]!=0)
  88.             {
  89.                 num++;
  90.             }
  91.         }
  92.         indegree[i]=num;
  93.     }
  94.     //把入度为0的顶点入栈
  95.     for (int i=0;i<vexnum;i++)
  96.     {
  97.         if (!indegree[i])
  98.         {
  99.             stack[++top]=i;//顶点的下标
  100.         }
  101.     }
  102.     //处理入度为0的结点:把入度为0的结点出栈,删除与之有关的边
  103.     while (top>-1)
  104.     {
  105.         int x = stack[top--];
  106.         cout<<vexs[x];
  107.         count++;
  108.         //把与下标为x的顶点有关的边都去掉(出边),并改变对应结点的入度
  109.         for (int i=0;i<vexnum;i++)
  110.         {
  111.             if (arcs[x][i]!=0)
  112.             {
  113.                 arcs[x][i]=0;//删除到下标为i的顶点的边,这时此顶点的入度减一
  114.                 indegree[i]–;
  115.                 if (!indegree[i])//顶点的入度为0,则入栈
  116.                 {
  117.                     stack[++top]=i;
  118.                 }
  119.             }
  120.         }
  121.     }
  122.     cout<<endl;
  123.     if (count == vexnum) //能拓扑排序
  124.     {
  125.         return true;
  126.     }
  127.     return false;
  128. }
  129. /*
  130. 检查图中是不是有环
  131. 思想:
  132. 能进行拓扑排序,则无环,反之有环
  133. */
  134. template<class VexType,class ArcType>
  135. void MGraph<VexType,ArcType>::CheckCircle()
  136. {
  137.     if (TopSort())
  138.     {
  139.         cout<<”无环!”<<endl;
  140.     }
  141.     else
  142.     {
  143.         cout<<”有环!”<<endl;
  144.     }
  145. }
  146. int main()
  147. {
  148.     MGraph<char,int> G;
  149.     G.CreateGraph();
  150.     G.CheckCircle();
  151.     system(“pause”);
  152.     return 1;
  153. }

测试:


有向图:

结果:

2、深度遍历 + 回溯

思想:用回溯法,遍历时,如果遇到了之前访问过的结点,则图中存在环。

代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int MAX_Vertex_Num = 20;
  4. template<class VexType,class ArcType>
  5. class MGraph
  6. {
  7. public:
  8.     void CreateGraph();//创建图
  9.     int LocateVex(VexType v);//返回顶点v所在顶点向量中的位置(下标)
  10.     bool CheckCircle();//检查图中有无环
  11. private:
  12.     VexType vexs[MAX_Vertex_Num];//顶点向量
  13.     ArcType arcs[MAX_Vertex_Num][MAX_Vertex_Num]; //这里把邻接矩阵类型用模板表示,主要是为了处理有权值的情况,比如:权值可以为小数(代价),也可以为整数
  14.     int vexnum;//顶点数
  15.     int arcnum;//边数
  16. private:
  17.     void CheckCircle(int u,bool& isExist,bool visited[MAX_Vertex_Num],bool Isvisited[MAX_Vertex_Num]);
  18. };
  19. template<class VexType,class ArcType>
  20. void MGraph<VexType,ArcType>::CreateGraph()
  21. {
  22.     VexType first;
  23.     VexType Secend;
  24.     cout<<”请输入顶点数:”;
  25.     cin>>vexnum;
  26.     cout<<”请输入边数:”;
  27.     cin>>arcnum;
  28.     cout<<”请输入各个顶点值:”;
  29.     for (int i=0;i<vexnum;i++)
  30.     {
  31.         cin>>vexs[i];
  32.     }
  33.     //初始化邻接矩阵
  34.     for (int i=0;i<arcnum;i++)
  35.     {
  36.         for (int j=0;j<arcnum;j++)
  37.         {
  38.             arcs[i][j]=0;
  39.         }
  40.     }
  41.     cout<<”请输入边的信息:”<<endl;
  42.     for (int i=0;i<arcnum;i++)
  43.     {
  44.         cin>>first>>Secend;
  45.         //如果边有权值的话,则还应该输入权值
  46.         int x = LocateVex(first);
  47.         int y = LocateVex(Secend);
  48.         arcs[x][y]=1;//如果是有权的话,这里应该是arc[x][y]=权值
  49.     }
  50. }
  51. /*
  52. 参数:v:表示顶点向量中一个值
  53. 函数返回值:函数返回v在顶点向量中的下标
  54. */
  55. template<class VexType,class ArcType>
  56. int MGraph<VexType,ArcType>::LocateVex(VexType v)
  57. {
  58.     for (int i=0;i<vexnum;i++)
  59.     {
  60.         if (vexs[i]==v)
  61.         {
  62.             return i;
  63.         }
  64.     }
  65.     return -1;
  66. }
  67. /*
  68. 思想:用回溯法,遍历时,如果遇到了之前访问过的结点,则图中存在环。
  69. 引入visited数组的原因:
  70.    1)在一次深度遍历时,检测路径上是否结点是否已经被检测到,如果被重复检测,则表示有环。
  71.    2)注意,在深度递归返回时,总是要把visited置为false。
  72. 引入Isvisited数组的原因:
  73.    1)用于记录目前为止深度遍历过程中遇到的顶点。
  74.    2)因为,我们不一定以所有结点为起始点都进行一次深度遍历。
  75.    3)举例,在结点A为起点,进行深度遍历时,遇到了结点B,此时Isvisited在A和B两个位置都为true。
  76.    那么没遇到环,那么我们就不用再以B为起始点继续进行一次深度遍历了,
  77.    因为A为起点的深度遍历已经验证不会有环了。
  78. */
  79. template<class VexType,class ArcType>
  80. void MGraph<VexType,ArcType>::CheckCircle(int u,bool& isExist,bool visited[MAX_Vertex_Num],bool Isvisited[MAX_Vertex_Num])
  81. {
  82.     visited[u]=true;
  83.     Isvisited[u]=true;
  84.     for (int j=0;j<vexnum;j++)
  85.     {
  86.         if (arcs[u][j]==1)
  87.         {
  88.             if (visited[j]==false)
  89.             {
  90.                 CheckCircle(j,isExist,visited,Isvisited);
  91.             }
  92.             else
  93.             {
  94.                 isExist = true;
  95.             }
  96.         }
  97.     }
  98.     visited[u]=false;//回溯,如果不写就变成一半的深度遍历,不能进行判断是否有边存在
  99. }
  100. template<class VexType,class ArcType>
  101. bool MGraph<VexType,ArcType>::CheckCircle()
  102. {
  103.     bool isExist = false;
  104.     bool Isvisited[MAX_Vertex_Num]={false};
  105.     bool visited[MAX_Vertex_Num]={false};
  106.     for (int i=0;i<vexnum;i++)
  107.     {
  108.         if (Isvisited[i]==false)
  109.         {
  110.             CheckCircle(i,isExist,visited,Isvisited);
  111.             if (isExist)
  112.             {
  113.                 return true;
  114.             }
  115.         }
  116.     }
  117.     return isExist;
  118. }
  119. int main()
  120. {
  121.     MGraph<char,int> G;
  122.     G.CreateGraph();
  123.     if (G.CheckCircle())
  124.     {
  125.         cout<<”图存在环!”<<endl;
  126.     }
  127.     else
  128.     {
  129.         cout<<”图不存在环!”<<endl;
  130.     }
  131.     system(“pause”);
  132.     return 1;
  133. }

结果测试:

3、深度遍历 + 判断后退边

思想:用DFS(深度优先遍历),判断是否有后退边,若有,则存在环

具体来说,在遍历顶点的每一条边时,判断一下这个边的顶点是不是在栈中,如果在栈中,说明之前已经访问过了,这里再次访问,说明有环存在

判断后退边时,借助一个栈和一个数组

栈:即可以用来输出环

数组:inStack判断是否在栈中

源代码:

  1. #include <iostream>
  2. using namespace std;
  3. const int MAX_Vertex_Num = 20;
  4. template<class VexType,class ArcType>
  5. class MGraph
  6. {
  7. public:
  8.     void CreateGraph();//创建图
  9.     int LocateVex(VexType v);//返回顶点v所在顶点向量中的位置(下标)
  10.     void CheckCircle();
  11. private:
  12.     VexType vexs[MAX_Vertex_Num];//顶点向量
  13.     ArcType arcs[MAX_Vertex_Num][MAX_Vertex_Num]; //这里把邻接矩阵类型用模板表示,主要是为了处理有权值的情况,比如:权值可以为小数(代价),也可以为整数
  14.     int vexnum;//顶点数
  15.     int arcnum;//边数
  16. private:
  17.     void DFS(int x,bool visited[MAX_Vertex_Num],int stack[MAX_Vertex_Num],int& top,bool inStack[MAX_Vertex_Num],int& count);
  18. };
  19. template<class VexType,class ArcType>
  20. void MGraph<VexType,ArcType>::CreateGraph()
  21. {
  22.     VexType first;
  23.     VexType Secend;
  24.     cout<<”请输入顶点数:”;
  25.     cin>>vexnum;
  26.     cout<<”请输入边数:”;
  27.     cin>>arcnum;
  28.     cout<<”请输入各个顶点值:”;
  29.     for (int i=0;i<vexnum;i++)
  30.     {
  31.         cin>>vexs[i];
  32.     }
  33.     //初始化邻接矩阵
  34.     for (int i=0;i<arcnum;i++)
  35.     {
  36.         for (int j=0;j<arcnum;j++)
  37.         {
  38.             arcs[i][j]=0;
  39.         }
  40.     }
  41.     cout<<”请输入边的信息:”<<endl;
  42.     for (int i=0;i<arcnum;i++)
  43.     {
  44.         cin>>first>>Secend;
  45.         //如果边有权值的话,则还应该输入权值
  46.         int x = LocateVex(first);
  47.         int y = LocateVex(Secend);
  48.         arcs[x][y]=1;//如果是有权的话,这里应该是arc[x][y]=权值
  49.     }
  50. }
  51. /*
  52. 参数:v:表示顶点向量中一个值
  53. 函数返回值:函数返回v在顶点向量中的下标
  54. */
  55. template<class VexType,class ArcType>
  56. int MGraph<VexType,ArcType>::LocateVex(VexType v)
  57. {
  58.     for (int i=0;i<vexnum;i++)
  59.     {
  60.         if (vexs[i]==v)
  61.         {
  62.             return i;
  63.         }
  64.     }
  65.     return -1;
  66. }
  67. /*
  68. 检查图中是不是有回向边
  69. 思想:
  70. 如果有回向边,则无环,反之有环
  71. */
  72. template<class VexType,class ArcType>
  73. void MGraph<VexType,ArcType>::CheckCircle()
  74. {
  75.     int count=0;//环的个数
  76.     int top=-1;
  77.     int stack[MAX_Vertex_Num];
  78.     bool inStack[MAX_Vertex_Num]={false};
  79.     bool visited[MAX_Vertex_Num]={false};
  80.     for (int i=0;i<vexnum;i++)
  81.     {
  82.         if (!visited[i])
  83.         {
  84.             DFS(i,visited,stack,top,inStack,count);
  85.         }
  86.     }
  87. }
  88. template<class VexType,class ArcType>
  89. void MGraph<VexType,ArcType>::DFS(int x,bool visited[MAX_Vertex_Num],int stack[MAX_Vertex_Num],int& top,bool inStack[MAX_Vertex_Num],int& count)
  90. {
  91.     visited[x]=true;
  92.     stack[++top]=x;
  93.     inStack[x]=true;
  94.     for (int i=0;i<vexnum;i++)
  95.     {
  96.         if (arcs[x][i]!=0)//有边
  97.         {
  98.             if (!inStack[i])
  99.             {
  100.                 DFS(i,visited,stack,top,inStack,count);
  101.             }
  102.             else //条件成立,表示下标为x的顶点到 下标为i的顶点有环
  103.             {
  104.                 count++;
  105.                 cout<<”第”<<count<<”环为:”;
  106.                 //从i到x是一个环,top的位置是x,下标为i的顶点在栈中的位置要寻找一下
  107.                 //寻找起始顶点下标在栈中的位置
  108.                 int t=0;
  109.                 for (t=top;stack[t]!=i;t–);
  110.                 //输出环中顶点
  111.                 for (int j=t;j<=top;j++)
  112.                 {
  113.                     cout<<vexs[stack[j]];
  114.                 }
  115.                 cout<<endl;
  116.             }
  117.         }
  118.     }
  119.     //处理完结点后,退栈
  120.     top–;
  121.     inStack[x]=false;
  122. }
  123. int main()
  124. {
  125.     MGraph<char,int> G;
  126.     G.CreateGraph();
  127.     G.CheckCircle();
  128.     system(“pause”);
  129.     return 1;
  130. }

结果测试:

结果: