判断一个图是否有环(无向图、有向图)

原文地址:http://www.cnblogs.com/xwdreamer/archive/2011/06/11/2297008.html

1. 无向图

方法1:

如果存在回路,则必存在一个子图,是一个环路。环路中所有顶点的度>=2。
n算法:
第一步:删除所有度<=1的顶点及相关的边,并将另外与这些边相关的其它顶点的度减一。
第二步:将度数变为1的顶点排入队列,并从该队列中取出一个顶点重复步骤一。
如果最后还有未删除顶点,则存在环,否则没有环。

n算法分析:
由于有m条边,n个顶点。
i)如果m>=n,则根据图论知识可直接判断存在环路。(证明:如果没有环路,则该图必然是k棵树 k>=1。根据树的性质,边的数目m = n-k。k>=1,所以m < n)
ii)如果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)。

方法3:

假定:图顶点个数为M,边条数为E

遍历一遍,判断图分为几部分(假定为P部分,即图有 P 个连通分量)
对于每一个连通分量,如果无环则只能是树,即:边数=结点数-1
只要有一个满足 边数 > 结点数-1
原图就有环
将P个连通分量的不等式相加,就得到:
P1:E1=M1-1
P2:E2=M2-1

PN:EN>MN-1
所有边数(E) > 所有结点数(M) – 连通分量个数(P)
即: E + P > M 所以只要判断结果 E + P > M 就表示原图有环,否则无环.

实例代码如下:

#include<iostream> 
#include<malloc.h> 
using namespace std;  
#define maxNum 100 //定义邻接举证的最大定点数 
int visited[maxNum];//通过visited数组来标记这个顶点是否被访问过,0表示未被访问,1表示被访问 
int DFS_Count;//连通部件个数,用于测试无向图是否连通,DFS_Count=1表示只有一个连通部件,所以整个无向图是连通的 
int pre[maxNum];  
int post[maxNum];  
int point;//pre和post的值 

//图的邻接矩阵表示结构 
typedef struct  
{  
    char v[maxNum];//图的顶点信息 
    int e[maxNum][maxNum];//图的顶点信息 
    int vNum;//顶点个数 
    int eNum;//边的个数 
}graph;  
void createGraph(graph *g);//创建图g 
void DFS(graph *g);//深度优先遍历图g 
void dfs(graph *g,int i);//从顶点i开始深度优先遍历与其相邻的点 
void dfs(graph *g,int i)  
{  
    //cout<<"顶点"<<g->v[i]<<"已经被访问"<<endl; 
    cout<<"顶点"<<i<<"已经被访问"<<endl;  
    visited[i]=1;//标记顶点i被访问 
    pre[i]=++point;  
    for(int j=1;j<=g->vNum;j++)  
    {  
        if(g->e[i][j]!=0&&visited[j]==0)  
            dfs(g,j);  
    }  
    post[i]=++point;  
}  

void DFS(graph *g)  
{  
    int i;  
    //初始化visited数组,表示一开始所有顶点都未被访问过 
    for(i=1;i<=g->vNum;i++)  
    {  
        visited[i]=0;  
        pre[i]=0;  
        post[i]=0;  
    }  
    //初始化pre和post 
    point=0;  
    //初始化连通部件数为0 
    DFS_Count=0;  
    //深度优先搜索 
    for(i=1;i<=g->vNum;i++)  
    {  
        if(visited[i]==0)//如果这个顶点为被访问过,则从i顶点出发进行深度优先遍历 
        {  
            DFS_Count++;//统计调用void dfs(graph *g,int i);的次数 
            dfs(g,i);  
        }  
    }  
}  
void createGraph(graph *g)//创建图g 
{  
    cout<<"正在创建无向图..."<<endl;  
    cout<<"请输入顶点个数vNum:";  
    cin>>g->vNum;  
    cout<<"请输入边的个数eNum:";  
    cin>>g->eNum;  
    int i,j;  
    //输入顶点信息 
    //cout<<"请输入顶点信息:"<<endl; 
    //for(i=0;i<g->vNum;i++) 
    // cin>>g->v[i]; 
    //初始画图g 
    for(i=1;i<=g->vNum;i++)  
        for(j=1;j<=g->vNum;j++)  
            g->e[i][j]=0;  
    //输入边的情况 
    cout<<"请输入边的头和尾"<<endl;  
    for(int k=0;k<g->eNum;k++)  
    {  
        cin>>i>>j;  
        g->e[i][j]=1;  
        g->e[j][i]=1;//无向图对称 
    }  
}  
int main()  
{  
    graph *g;  
    g=(graph*)malloc(sizeof(graph));  
    createGraph(g);//创建图g 
    DFS(g);//深度优先遍历 
    //连通部件数,用于判断是否连通图 
    cout<<"连通部件数量:";  
    cout<<DFS_Count<<endl;  
    if(DFS_Count==1)  
        cout<<"图g是连通图"<<endl;  
    else if(DFS_Count>1)  
        cout<<"图g不是连通图"<<endl;  
    //各顶点的pre和post值 
    for(int i=1;i<=g->vNum;i++)  
        cout<<"顶点"<<i<<"的pre和post分别为:"<<pre[i]<<" "<<post[i]<<endl;  
    //cout<<endl; 
    //判断无向图中是否有环 
    if(g->eNum+DFS_Count>g->vNum)  
        cout<<"图g中存在环"<<endl;  
    else  
        cout<<"图g中不存在环"<<endl;  
    int k;  
    cin>>k;  
    return 0;  
}  
/* 输入: 正在创建无向图... 请输入顶点个数vNum:10 请输入边的个数eNum:9 请输入边的头和尾 1 2 1 4 2 5 2 6 4 7 5 9 6 3 7 8 9 10 */  

注意:有向图不能使用此方法。比如1->2,1-3,2->3,4->5,如果使用上述方法会判定为含有还,但并非如此。

2. 有向图

主要有深度优先和拓扑排序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的推导,没有环。 在程序中加上一些特殊的处理,即可以找出图中有几个环,并记录每个环的路径

改进DFS算法代码示例(判断是否是一个有向无环图)

#include<iostream> 
#include<malloc.h> 
using namespace std;  
#define maxNum 100 //定义邻接举证的最大定点数 
int pre[maxNum];  
int post[maxNum];  
int point=0;//pre和post的值 
bool is_DAG=true;//标识位,表示有向无环图 
/* 顶点颜色表 color[u]    0 白色,未被访问过的节点标白色    -1 灰色,已经被访问过一次的节点标灰色    1 黑色,当该节点的所有后代都被访问过标黑色 反向边:    如果第一次访问(u,v)时v为灰色,则(u,v)为反向边。在对图的深度优先搜索中没有发现    反向边,则该图没有回路 程序判断依据: 仍然是按图的节点深度遍历,访问到V时,V若被访问过,那么有2种状态: color[u]=-1,程序跳出,存在环 color[u]=1,程序继续,这不是环 时间复杂度:O(n+e) */  
int color[maxNum];//顶点颜色表 color[u] 
//图的邻接矩阵表示结构 
typedef struct  
{  
    char v[maxNum];//图的顶点信息 
    int e[maxNum][maxNum];//图的顶点信息 
    int vNum;//顶点个数 
    int eNum;//边的个数 
}graph;  
void createGraph(graph *g);//创建图g 
void DFS(graph *g);//深度优先遍历图g 
void dfs(graph *g,int i);//从顶点i开始深度优先遍历与其相邻的点 
void dfs(graph *g,int i)  
{  
    //cout<<"顶点"<<g->v[i]<<"已经被访问"<<endl; 
    cout<<"顶点"<<i<<"已经被访问"<<endl;  
    color[i]=-1;  
    pre[i]=++point;  
    for(int j=1;j<=g->vNum;j++)  
    {  
        if(g->e[i][j]!=0)  
        {     
            if(color[j]==-1)//探索到回边,存在环 
            {  
                is_DAG=false;//不是有向无环图 
            }  
            else if(color[j]==0)  
                dfs(g,j);  
        }  
    }  
    post[i]=++point;  
    color[i]=1;//表示i的后裔节点都被访问过 
}  
void DFS(graph *g)  
{  
    int i;  
    //初始化color数组,表示一开始所有顶点都未被访问过,//初始化pre和post 
    for(i=1;i<=g->vNum;i++)  
    {  
        color[i]=0;  
        pre[i]=0;  
        post[i]=0;  
    }  
    //深度优先搜索 
    for(i=1;i<=g->vNum;i++)  
    {  
        if(color[i]==0)//如果这个顶点为被访问过,则从i顶点出发进行深度优先遍历 
        {  
            dfs(g,i);  

        }  
    }  
}  
void createGraph(graph *g)//创建图g 
{  
    cout<<"正在创建无向图..."<<endl;  
    cout<<"请输入顶点个数vNum:";  
    cin>>g->vNum;  
    cout<<"请输入边的个数eNum:";  
    cin>>g->eNum;  
    int i,j;  
    //初始画图g 
    for(i=1;i<=g->vNum;i++)  
        for(j=1;j<=g->vNum;j++)  
            g->e[i][j]=0;  
    //输入边的情况 
    cout<<"请输入边的头和尾"<<endl;  
    for(int k=1;k<=g->eNum;k++)  
    {  
        cin>>i>>j;  
        g->e[i][j]=1;  
    }  
}  
int main()  
{  
    graph *g;  
    g=(graph*)malloc(sizeof(graph));  
    createGraph(g);//创建图g 
    DFS(g);//深度优先遍历 
    //各顶点的pre和post值 
    for(int i=1;i<=g->vNum;i++)  
        cout<<"顶点"<<i<<"的pre和post分别为:"<<pre[i]<<" "<<post[i]<<endl;  
    //判断是否是有向无环图 
    if(is_DAG)  
        cout<<"图g是有向无环图,没有环"<<endl;  
    else  
        cout<<"图g不是有向无环图,存在环"<<endl;  
    int k;  
    cin>>k;  
    return 0;  
}  
/* 输入1: 正在创建无向图... 请输入顶点个数vNum:3 请输入边的个数eNum:3 请输入边的头和尾 1 2 1 3 3 2 输入2: 正在创建无向图... 请输入顶点个数vNum:4 请输入边的个数eNum:4 请输入边的头和尾 1 2 2 3 3 4 4 2 */  
点赞