判断图里有环

来源:互联网 发布:java培训班要多少钱 编辑:程序博客网 时间:2024/06/06 03:10

无向图:

法1:

  • 如果存在回路,则必存在一个子图,是一个环路。环路中所有顶点的度>=2。   
  • n算法:   
  •      第一步:删除所有度<=1的顶点及相关的边,并将另外与这些边相关的其它顶点的度减一。   
  •      第二步:将度数变为1的顶点排入队列,并从该队列中取出一个顶点重复步骤一。   
  •      如果最后还有未删除顶点,则存在环,否则没有环。   
  • n算法分析:   
  •      由于有m条边,n个顶点。如果m>=n,则根据图论知识可直接判断存在环路。   
  •     (证明:如果没有环路,则该图必然是k棵树 k>=1。根据树的性质,边的数目m = n-k。k>=1,所以:m<n)   
  •      如果m<n 则按照上面的算法每删除一个度为0的顶点操作一次(最多n次),或每删除一个度为1的顶点(同时删一条边)操作一次(最多m次)。这两种操作的总数不会超过m+n。由于m<n,所以算法复杂度为O(n) 

     

    另:

    该方法,算法复杂度不止O(V),首先初始时刻统计所有顶点的度的时候,复杂度为(V + E),即使在后来的循环中E>=V,这样算法的复杂度也只能为O(V + E)。其次,在每次循环时,删除度为1的顶点,那么就必须将与这个顶点相连的点的度减一,并且执行delete node from list[list[node]],这里查找的复杂度为list[list[node]]的长度,只有这样才能保证当degree[i]=1时,list[i]里面只有一个点。这样最差的复杂度就为O(EV)了。

    法2:

    DFS搜索图,图中的边只可能是树边或反向边,一旦发现反向边,则表明存在环。该算法的复杂度为O(V)。

     

    有向图:

    主要有深度优先和拓扑排序2中方法

     1、拓扑排序,如果能够用拓扑排序完成对图中所有节点的排序的话,就说明这个图中没有环,而如果不能完成,则说明有环。

        2、可以用Strongly Connected Components来做,我们可以回忆一下强连通子图的概念,就是说对于一个图的某个子图,该子图中的任意u->v,必有v->u,则这是一个强连通子图。这个限定正好是环的概念。所以我想,通过寻找图的强连通子图的方法应该可以找出一个图中到底有没有环、有几个环。

        3、就是用一个改进的DFS

        刚看到这个问题的时候,我想单纯用DFS就可以解决问题了。但细想一下,是不能够的。如果题目给出的是一个无向图,那么OK,DFS是可以解决的。但无向图得不出正确结果的。比如:A->B,A->C->B,我们用DFS来处理这个图,我们会得出它有环,但其实没有。

        我们可以对DFS稍加变化,来解决这个问题。解决的方法如下:

        图中的一个节点,根据其C[N]的值,有三种状态:

        0,此节点没有被访问过

        -1,被访问过至少1次,其后代节点正在被访问中

        1,其后代节点都被访问过。

        按照这样的假设,当按照DFS进行搜索时,碰到一个节点时有三种可能:

        1、如果C[V]=0,这是一个新的节点,不做处理

        2、如果C[V]=-1,说明是在访问该节点的后代的过程中访问到该节点本身,则图中有环。

        3、如果C[V]=1,类似于2的推导,没有环。    在程序中加上一些特殊的处理,即可以找出图中有几个环,并记录每个环的路径

     

     

    判断有向图是否有环有三种方法:拓扑排序、深度遍历+回溯、深度遍历 + 判断后退边

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

    1、拓扑排序

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

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

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

    源代码:

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

    测试:

    有向图:

     

    结果:

    2、深度遍历 + 回溯

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

    代码:

    [cpp] view plaincopyprint?
    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.   
    20. template<class VexType,class ArcType>  
    21. void MGraph<VexType,ArcType>::CreateGraph()  
    22. {  
    23.     VexType first;  
    24.     VexType Secend;  
    25.     cout<<"请输入顶点数:";  
    26.     cin>>vexnum;  
    27.     cout<<"请输入边数:";  
    28.     cin>>arcnum;  
    29.     cout<<"请输入各个顶点值:";  
    30.     for (int i=0;i<vexnum;i++)  
    31.     {  
    32.         cin>>vexs[i];  
    33.     }  
    34.     //初始化邻接矩阵   
    35.     for (int i=0;i<arcnum;i++)  
    36.     {  
    37.         for (int j=0;j<arcnum;j++)  
    38.         {  
    39.             arcs[i][j]=0;  
    40.         }  
    41.     }  
    42.     cout<<"请输入边的信息:"<<endl;  
    43.     for (int i=0;i<arcnum;i++)  
    44.     {  
    45.         cin>>first>>Secend;  
    46.         //如果边有权值的话,则还应该输入权值  
    47.         int x = LocateVex(first);  
    48.         int y = LocateVex(Secend);  
    49.         arcs[x][y]=1;//如果是有权的话,这里应该是arc[x][y]=权值  
    50.     }  
    51. }   
    52. /* 
    53. 参数:v:表示顶点向量中一个值 
    54. 函数返回值:函数返回v在顶点向量中的下标 
    55. */  
    56. template<class VexType,class ArcType>  
    57. int MGraph<VexType,ArcType>::LocateVex(VexType v)  
    58. {  
    59.     for (int i=0;i<vexnum;i++)  
    60.     {  
    61.         if (vexs[i]==v)  
    62.         {  
    63.             return i;  
    64.         }  
    65.     }  
    66.     return -1;  
    67. }  
    68.   
    69. /* 
    70. 思想:用回溯法,遍历时,如果遇到了之前访问过的结点,则图中存在环。 
    71. */  
    72. template<class VexType,class ArcType>  
    73. void MGraph<VexType,ArcType>::CheckCircle(int u,bool& isExist,bool visited[MAX_Vertex_Num],bool Isvisited[MAX_Vertex_Num])  
    74. {  
    75.     visited[u]=true;  
    76.     Isvisited[u]=true;  
    77.     for (int j=0;j<vexnum;j++)  
    78.     {  
    79.         if (arcs[u][j]==1)  
    80.         {  
    81.             if (visited[j]==false)  
    82.             {  
    83.                 CheckCircle(j,isExist,visited,Isvisited);  
    84.             }  
    85.             else  
    86.             {  
    87.                 isExist = true;  
    88.             }  
    89.         }  
    90.     }  
    91.     visited[u]=false;//回溯,如果不写就变成一半的深度遍历,不能进行判断是否有边存在  
    92. }  
    93.   
    94. template<class VexType,class ArcType>  
    95. bool MGraph<VexType,ArcType>::CheckCircle()  
    96. {  
    97.     bool isExist = false;  
    98.     bool Isvisited[MAX_Vertex_Num]={false};  
    99.     bool visited[MAX_Vertex_Num]={false};  
    100.     for (int i=0;i<vexnum;i++)  
    101.     {  
    102.         if (Isvisited[i]==false)  
    103.         {  
    104.             CheckCircle(i,isExist,visited,Isvisited);  
    105.             if (isExist)  
    106.             {  
    107.                 return true;  
    108.             }  
    109.         }  
    110.     }  
    111.     return isExist;  
    112. }  
    113.   
    114. int main()  
    115. {  
    116.     MGraph<char,int> G;  
    117.     G.CreateGraph();  
    118.     if (G.CheckCircle())  
    119.     {  
    120.         cout<<"图存在环!"<<endl;  
    121.     }  
    122.     else  
    123.     {  
    124.         cout<<"图不存在环!"<<endl;  
    125.     }  
    126.     system("pause");  
    127.     return 1;  
    128. }  

    结果测试:

     图:

     

    结果:

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

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

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

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

    栈:即可以用来输出环

    数组:inStack判断是否在栈中

    源代码:

    [cpp] view plaincopyprint?
    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. };  
    20.   
    21. template<class VexType,class ArcType>  
    22. void MGraph<VexType,ArcType>::CreateGraph()  
    23. {  
    24.     VexType first;  
    25.     VexType Secend;  
    26.     cout<<"请输入顶点数:";  
    27.     cin>>vexnum;  
    28.     cout<<"请输入边数:";  
    29.     cin>>arcnum;  
    30.     cout<<"请输入各个顶点值:";  
    31.     for (int i=0;i<vexnum;i++)  
    32.     {  
    33.         cin>>vexs[i];  
    34.     }  
    35.     //初始化邻接矩阵  
    36.     for (int i=0;i<arcnum;i++)  
    37.     {  
    38.         for (int j=0;j<arcnum;j++)  
    39.         {  
    40.             arcs[i][j]=0;  
    41.         }  
    42.     }  
    43.     cout<<"请输入边的信息:"<<endl;  
    44.     for (int i=0;i<arcnum;i++)  
    45.     {  
    46.         cin>>first>>Secend;  
    47.         //如果边有权值的话,则还应该输入权值  
    48.         int x = LocateVex(first);  
    49.         int y = LocateVex(Secend);  
    50.         arcs[x][y]=1;//如果是有权的话,这里应该是arc[x][y]=权值  
    51.     }  
    52. }   
    53. /* 
    54. 参数:v:表示顶点向量中一个值 
    55. 函数返回值:函数返回v在顶点向量中的下标 
    56. */  
    57. template<class VexType,class ArcType>  
    58. int MGraph<VexType,ArcType>::LocateVex(VexType v)  
    59. {  
    60.     for (int i=0;i<vexnum;i++)  
    61.     {  
    62.         if (vexs[i]==v)  
    63.         {  
    64.             return i;  
    65.         }  
    66.     }  
    67.     return -1;  
    68. }  
    69.   
    70. /* 
    71. 检查图中是不是有回向边 
    72. 思想: 
    73. 如果有回向边,则无环,反之有环 
    74. */  
    75. template<class VexType,class ArcType>  
    76. void MGraph<VexType,ArcType>::CheckCircle()  
    77. {  
    78.     int count=0;//环的个数  
    79.     int top=-1;  
    80.     int stack[MAX_Vertex_Num];  
    81.     bool inStack[MAX_Vertex_Num]={false};  
    82.     bool visited[MAX_Vertex_Num]={false};  
    83.     for (int i=0;i<vexnum;i++)  
    84.     {  
    85.         if (!visited[i])  
    86.         {  
    87.             DFS(i,visited,stack,top,inStack,count);  
    88.         }  
    89.     }  
    90. }  
    91.   
    92. template<class VexType,class ArcType>  
    93. 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)  
    94. {  
    95.     visited[x]=true;  
    96.     stack[++top]=x;  
    97.     inStack[x]=true;  
    98.     for (int i=0;i<vexnum;i++)  
    99.     {  
    100.         if (arcs[x][i]!=0)//有边  
    101.         {  
    102.             if (!inStack[i])  
    103.             {  
    104.                 DFS(i,visited,stack,top,inStack,count);  
    105.             }  
    106.             else //条件成立,表示下标为x的顶点到 下标为i的顶点有环  
    107.             {  
    108.                 count++;  
    109.                 cout<<"第"<<count<<"环为:";  
    110.                 //从i到x是一个环,top的位置是x,下标为i的顶点在栈中的位置要寻找一下  
    111.                 //寻找起始顶点下标在栈中的位置  
    112.                 int t=0;  
    113.                 for (t=top;stack[t]!=i;t--);  
    114.                 //输出环中顶点  
    115.                 for (int j=t;j<=top;j++)  
    116.                 {  
    117.                     cout<<vexs[stack[j]];  
    118.                 }  
    119.                 cout<<endl;  
    120.             }  
    121.         }  
    122.     }  
    123.     //处理完结点后,退栈  
    124.     top--;  
    125.     inStack[x]=false;  
    126. }  
    127. int main()  
    128. {  
    129.     MGraph<char,int> G;  
    130.     G.CreateGraph();  
    131.     G.CheckCircle();  
    132.     system("pause");  
    133.     return 1;  
    134. }  

    结果测试:

    有向图:

     

    结果:
     

     

    1. //求图中环的个数  
    2. //由于图中每个点的出度只有1,所以不存在一个点处于两个环的交点  
    3. //因此,求环的个数时每个只需要考虑一次便可得出结果  
    4. //由于数据规模庞大,写成递归形式容易暴栈  
    5. //在读边的过程中先对自环进行预处理,之后对每个点进行不同的染色,对它的下一个点也染同样的颜色  
    6. //这样染下去如果发现下一个要染的点和正在染的颜色相同,则说明存在一个环  
    7. //换染色起点的同时也需要更换新的染色,才能保证对环的判断正确  
    8. #include<iostream>  
    9. #include<cstring>  
    10. using namespace std;  
    11. int next[1000001];//指向下一结点的指针     
    12. int vis[1000001];//对每个结点进行不同的标记  
    13. int ans,n,ringID,p;  
    14. void search()  
    15. {  
    16.     for(int i = 1;i <= n;++i)  
    17.     {  
    18.         if(vis[i] > 0)   continue;  
    19.         p = i;  
    20.         ++ringID;//对每一种环进行一种不同的标记,新的起点必须更换新的染色  
    21.         while(vis[p] == 0)//当前结点未被染色  
    22.         {  
    23.             vis[p] = ringID;//染色  
    24.             p = next[p];//指向下一个点  
    25.             if(vis[p] == ringID)//下一个点的颜色和当前染色相同,则说明存在一个环  
    26.                 ++ans;  
    27.         }  
    28.     }  
    29. }  
    30. int main()  
    31. {  
    32.     //freopen("in.txt","r",stdin);  
    33.     while(scanf("%d",&n) != EOF)  
    34.     {  
    35.         ans = 0;  
    36.         ringID = 1;  
    37.         memset(vis,0,sizeof(vis));  
    38.         for(int i = 1;i <= n;++i)  
    39.         {  
    40.             scanf("%d",&next[i]);  
    41.             if(next[i] == i)  
    42.             {  
    43.                 vis[i] = ringID++;//先对自环进行预处理  
    44.                 ans++;  
    45.             }  
    46.         }  
    47.         search();  
    48.         printf("%d/n",ans);  
    49.     }  
    50.     return 0;  
    51. }  

     

     

  • 原创粉丝点击