C++】判斷一個圖是否有環 無向圖 有向圖(轉載)
沒有找到原文出處,請參考一下連結:
一、無向圖:
方法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:
PS:此方法於2011-6-12補充
假定:圖頂點個數為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中方法
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的推導,沒有環。 在程式中加上一些特殊的處理,即可以找出圖中有幾個環,並記錄每個環的路徑
PS:此程式碼實現於2011-6-13補充
改進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
- */