1. 程式人生 > >求有向圖的強連通分量(c語言版)

求有向圖的強連通分量(c語言版)

 有向圖G:


1.選用何種結構儲存有向圖?

選用十字連結串列的結構來儲存圖

2.選用何種遍歷方式?

選用深度優先搜尋

3.思路

3.1 首先從圖G上某個頂點出發沿以該頂點為尾的弧深度優先搜尋,在頂點深度優先搜尋結束之時把該頂點存放到輔助陣列finished中(程式中實際存放的是該頂點在圖中的位置,當然頂點的位置是人為定的,a的位置是0,b的位置是1,c的位置是2,d的位置是3,頂點在圖的結構體中用陣列存起來)。

  解釋:假如從a頂點開始深度遍歷,過程是a->c->d,那麼finished陣列的值是 {3,2,0},深度遍歷完後,a頂點的訪問才結束,所以a最後退出這次遍歷,a存放到finished陣列最後。

3.2 然後從最後搜尋完成的頂點(即finished陣列中最後一個元素)出發,沿著以該頂點為頭的弧作逆向的深度優先搜尋遍歷,若以該頂點的這次深度遍歷不能訪問到圖中所有的頂點,就從陣列finished中最後完成搜尋的頂點(還未被訪問的)再次深度優先搜尋遍歷,以此類推,直到圖中所有的頂點都被訪問過。

 解釋:假如finished陣列中的值是{1,3,2,0},從0開始逆向的深度優先搜尋,能訪問到2,3,但是1訪問不到,假如1前面還有4,5頂點({4,5,1}),因為陣列finished是按退出深度優先搜尋的順序存進去的,所以1就是當前最後完成深度優先搜尋的頂點(相比4,5頂點),因此再從1頂點開始逆向深度優先遍歷,以此類推,直到所有頂點都訪問到。

3.3 打印出來的結果就是各強連通分量的頂點集。

4.原理

強連通分量的意思是 有向圖G=(V,{A}),(V代表頂點集,A代表弧),如果對於每一對vi,vj屬於V,vi不等於vj,從vi到vj和從vj到vi都儲存路徑,稱G是強連通圖,而有向圖中的極大連通子圖叫有向圖的強連通分量(如vi到vj有路徑,vj到vi也要有路徑,才叫強連通)。根據圖,從a頂點根據以弧尾是a的弧能訪問到d,那麼從a頂點根據以弧頭是a的弧也能訪問到d,說明a到d有路徑,d到a也有路徑,即是通的。

------------------------------------------------------------------------------------------------------------

原始碼地址:[email protected]:hglspace/OlGraph.git

-------------------------------------------------------------------------------------------------------------

myhead.c

#define MAX_VERTEX_NUM 4//人為設定頂點數為4

typedefint Bool;//自定義布林型變數

#define True 1//以訪問狀態

#define False 0//未訪問狀態

//十字連結串列儲存有向圖

struct ArcBox{//弧結點

int tailvex;//尾結點在圖中的位置

int headvex;//頭結點在圖中的位置

struct ArcBox * hlink;//弧頭相同的下一條弧

struct ArcBox * tlink;//弧尾相同的下一條弧

Bool mark;//訪問的標誌 True代表訪問過,False代表沒有訪問過

};

struct VexNode{//頂點結點

char data;//頂點資訊

struct ArcBox * firstin;//以該頂點為弧頭的第一條弧

struct ArcBox * firstout;//以該頂點為弧尾的第一條弧

};

struct OlGraph{

structVexNode xlist[MAX_VERTEX_NUM];//頂點結點陣列

int vexnum;//當前頂點數量

int arcnum;//當前弧的數量

};

//自定義標頭檔案

-----------------------------------------------------------

operateGraph.c

#include <stdio.h>

#include "myhead.h"

#include <stdlib.h>

Bool visits[MAX_VERTEX_NUM];//定義頂點輔助陣列,記錄頂點是否訪問過

int count=0;//定義全域性變數,輔助陣列finished使用

structArcBox * edg[MAX_VERTEX_NUM];//定義全域性弧指標陣列,記錄弧地址,方便把弧的是否訪問過的狀態重置

/*

初始化有向圖

*/

structOlGraph init(void){

int i,j,k,m;

char data1,data2;//用來接受頂點的資訊,根據資訊查詢頂點的位置

struct OlGraph g;

int locateVex(struct OlGraph g,char data);//宣告查詢頂點位置的函式

printf("請輸入圖的頂點數:");

scanf("%d",&g.vexnum);

printf("請輸入圖的邊數:");

scanf("%d",&g.arcnum);

//開始初始化頂點和弧

for(i=0;i<g.vexnum;i++){//初始化頂點

printf("請輸入第%d個頂點:",i+1);

scanf(" %c",&g.xlist[i].data);

g.xlist[i].firstin=NULL;

g.xlist[i].firstout=NULL;

visits[i]=False;

}

for(j=0;j<g.arcnum;j++){//初始化弧

struct ArcBox * p = malloc(sizeof(struct ArcBox));//開闢儲存弧的空間

if(p==NULL){//如果開闢失敗,程式終止

exit(-1);

}

printf("請輸入第%d條邊的起點:",j+1);

scanf(" %c",&data1);

printf("請輸入第%d條邊的終點:",j+1);

scanf(" %c",&data2);

k=locateVex(g,data1);

m=locateVex(g,data2);

p->headvex=m;//對弧的頭結點賦值

p->tailvex=k;//對弧的尾結點賦值

p->hlink=NULL;

p->tlink=NULL;

p->mark=False;//弧的狀態置為未訪問狀態

if(g.xlist[m].firstin==NULL){//m結點是頭結點,需要動態改變該結點第一個以該結點為頭結點的弧

g.xlist[m].firstin=p;

}else{

p->hlink=g.xlist[m].firstin;

g.xlist[m].firstin=p;

}

if(g.xlist[k].firstout==NULL){//k結點是尾結點,需要動態改變該結點第一個以該結點為尾結點的弧

g.xlist[k].firstout=p;

}else{

p->tlink=g.xlist[k].firstout;

g.xlist[k].firstout=p;

}

edg[j]=p;//把弧的指標儲存到 弧指標陣列中

}

return g;

}

/*

根據頂點的資訊查詢頂點在圖中的位置

*/

int locateVex(struct OlGraph g,char data){

int i;

for(i=0;data!=g.xlist[i].data;i++);

return i;

}

/*

生成有向圖的強連通分量的頂點集

*/

void DFSTraverse(struct OlGraph g){

int i,k,s;

int finished[MAX_VERTEX_NUM];//該陣列儲存退出DFS函式的頂點(按照退出的順序,先退出來的頂點先記錄。記錄的是頂點的位置),作用是:方便反向深度遍歷頂點

void DFS(struct OlGraph g,int i,int finished[]);//宣告深度遍歷頂點的函式

void DFSRever(structOlGraph g,int i,int finished[]);//宣告按照陣列finished中儲存的頂點,從finished[n-1]元素中儲存的頂點開始深度遍歷的函式 (n是儲存的頂點個數)

for(i=0;i<g.vexnum;i++){//開始遍歷頂點

if(visits[i]==True){//如果訪問過,就跳過

continue;

}

DFS(g,i,finished);//呼叫函式

//finished[count++]=i;

}

for(k=0;k<g.vexnum;k++){//把頂點訪問的標誌重置到開始狀態

visits[k]=False;

}

for(s=0;s<g.arcnum;s++){//把弧的訪問標誌重置到開始狀態

edg[s]->mark=False;

}

while(count>=1){//按照finished陣列中存放的頂點,開始深度遍歷

if(visits[finished[count-1]]==True){//如果該頂點訪問過就跳過

continue;

}

visits[finished[count-1]]=True;//置該頂點的訪問狀態為已訪問

printf("%c",g.xlist[finished[count-1]].data);//列印該頂點的資訊,其實就是對該頂點進行操作,訪問

DFSRever(g,finished[count-1],finished);//呼叫函式遍歷

printf("\n.........\n");

count--;//訪問下一頂點

}

}

void DFSRever(struct OlGraph g,int i,int finished[]){

int j;

int getTailAdjVex(struct OlGraph g,int i);//宣告查詢以該頂點為頭的弧的尾頂點的函式

for(j=getTailAdjVex(g, i);j>=0;j=getTailAdjVex(g, i)){

if(visits[j]==True){

continue;

}

visits[j]=True;

DFSRever(g,j,finished);//遞迴呼叫,查詢i結點的鄰接點的鄰接點

printf("%c",g.xlist[j].data);//訪問i結點的鄰接點

}

}

void DFS(struct OlGraph g,int i,int finished[]){

visits[i]=True;

int w;

int getHeadAdjVex(struct OlGraph g,int i);//宣告以該結點為尾的弧的頭結點的函式

for(w=getHeadAdjVex(g,i);w>=0;w=getHeadAdjVex(g,i)){

if(visits[w]==True){

continue;

}

DFS(g,w,finished);//遞迴呼叫,其實就是深度遍歷

}

finished[count++]=i;//退出函式時把該結點儲存到finished陣列中

}

/*

查詢以該結點為尾的弧的頭結點

*/

int getHeadAdjVex(struct OlGraph g,int i){

structArcBox * p=g.xlist[i].firstout;

if(p==NULL){

return -1;

}

//for(p=g.xlist[i].firstout;p->mark==True;p=p->tlink);

while(p->mark==True){

p=p->tlink;

if(p==NULL){

return -1;

}

}

p->mark=True;

return p->headvex;

}

/*

查詢以該頂點為頭的弧的尾頂點

*/

int getTailAdjVex(struct OlGraph g,int i){

structArcBox * p=g.xlist[i].firstin;

if(p==NULL){

return -1;

}

//for(;p->mark==True;p=p->hlink);

while(p->mark==True){

p=p->hlink;

if(p==NULL){

return -1;

}

}

p->mark=True;

return p->tailvex;

}

-----------------------------------------------------------------------

main.c

#include <stdio.h>

#include "myhead.h"

int main(int argc, const char * argv[]){

//printf("你好");

struct OlGraph init(void);

void DFSTraverse(struct OlGraph g);

struct OlGraph g=init();

DFSTraverse(g);

return0;

}

 (結語:我主要是做java開發的,為什麼用c語言來寫關於有向圖的操作呢?主要是大學裡學過c,懂一點基礎,為了能更深入瞭解c語言,業餘時間就用c寫點程式練習練習。個人感覺學好c語言有助於理解更高階的如java語言,java的虛擬機器就是有用c語言寫的。第二次記錄點東西到csdn,如有不妥的地方,請幫忙指出。計算機的世界真的很精彩,我還需要努力的探索!加油,各位)