小朋友學資料結構(16):基於鄰接矩陣的的深度優先遍歷和廣度優先遍歷
觀察下面兩個無向圖:

1.png
這兩個圖其實是一樣的,只是畫法不同罷了。第一張圖更有立體感,第二張圖更有層次感,並且把A點置為頂點(事實上圖的任何一點都可以做為頂點)。
一、用陣列來存放頂點
vexs[0] = ‘A’ vexs[1] = ‘B’ vexs[2] = ‘C’ vexs[3] = ‘D’ vexs[4] = ‘E’ vexs[5] = ‘F’ vexs[6] = ‘G’ vexs[7] = ‘H’ vexs[8] = ‘I’
二、用鄰接矩陣來表示邊

2.png
上面這個矩陣中,0表示每個頂點沒有到達自己的路徑。1表示兩個頂點之間有路徑,無窮大表示兩個頂點之間沒有路徑。
假如按照程式計數習慣,行或列都從0數起。
第0行第0列為0,表示A到它本身之間沒有路徑(這是人為規定的,因為A到它自身不需要路徑)。
第0行第1列為1,表示頂點A和B之間有路徑。
第0行第5列為1,表示頂點A和頂點F之間有路徑。
第0行其他列為無窮大,表示A到其它點之間沒有路徑。
……
因為是無向圖,鄰接矩陣必然有兩個特點:
① 對角線(左上角到右下角)上的元素值全為0.表示每個點到它自身沒有(或不需要)路徑。
② 其它的元素關於對角線對稱。
上面的鄰接矩陣,在程式設計時可以用二維陣列來實現:
arc[0][1] = arc[1][0] = 1; arc[0][5] = arc[5][0] =1; arc[1][2] = arc[2][1] = 1; arc[1][6] = arc[6][1] = 1; arc[1][8] = arc[8][1] = 1; arc[2][3] = arc[3][2] = 1; arc[2][8] = arc[8][2] = 1; arc[3][4] = arc[4][3] = 1; arc[3][6] = arc[6][3] = 1; arc[3][7] = arc[7][3] = 1; arc[3][8] = arc[8][3] = 1; arc[4][5] = arc[5][4] = 1; arc[4][7] = arc[7][4] = 1; arc[5][6] = arc[6][5] = 1; arc[6][7] = arc[7][6] = 1;
三、深度優先遍歷
可以使用遞迴的方法進行深度遍歷。
觀察圖(1)中的左圖,假如從頂點A開始,從A找到相鄰的B,從B找到相鄰的C,從C找到相鄰的D,從D找到相鄰的E,從E找到鄰接點F,從F找到相鄰的G,從G找到相鄰的H。
H有三個相鄰的點D、E、G。這三個點都已經遍歷過了。所以回退到上一頂點G。
G有三個相鄰的頂點D、F、H。這三個點也都已經遍歷過了。回退到上一頂點F。
F有兩個相鄰的頂點E、G,都已經遍歷過了。回退到上個頂點E。
E點有兩個相鄰的頂點D、F,都已經遍歷過了。回退到上個頂點D。
D點有五個相鄰的頂點C、E、H、G、I。除了I外,其餘四個頂點已經遍歷過了。所以這一次遍歷I。
I遍歷完之後回到D點,從D點回到C點。
C點有三個相鄰的頂點B、D、I,都已經遍歷過了,回退到B點。
B點有四個相鄰的頂點A、C、G、I,都已經遍歷過了,回退到A點。
A點有兩個相鄰的頂點B、F,都已經遍歷過了。遞迴都此結束。
得到深度優先遍歷的順序為:A B C D E F G H I
四、廣度優先遍歷
廣度優先遍歷需要藉助於另外的資料結構佇列。當把圖中的頂點放到佇列中時,表示這個頂點被遍歷了(可以把頂點的值打印出來)。
用圖1中的右圖來分析廣度優先遍歷更方便,因為右圖的層次結構更明顯。

3.png
起初,把點A放入佇列中,A被遍歷。如上圖中的(1)所示。
接著把隊首元素A出隊,把A的下一層的頂點B和F移進佇列,B和F被遍歷。如上圖中的(2)所示。
隊首元素B出隊,B的下一層頂點C,G,I相繼入隊,C、G和I被遍歷。如上圖中的(3)所示。
隊首元素F出隊,F的下一層頂點E入隊,E被遍歷。如上圖中的(5)所示。
隊首元素C出隊,C的下一層頂點D入隊,D被遍歷。如上圖中的(6)所示。
隊首元素G出隊,G的下一層有兩個頂點:D和H。D已在佇列裡,H入隊,H被遍歷。如上圖中的(4)所示。
隊首元素I出隊,I的下一層頂點D已在佇列裡,沒有新頂點入隊。如上圖中的(7)所示。
隊首元素E出隊,E的下一層頂點D和H都已在佇列裡,沒有新頂點入隊。如上圖中的(8)所示。
隊首元素D出隊,D沒有下一層頂點,所以沒有新頂點入隊。如上圖中的(9)所示。
隊首元素H出隊,H沒有下一層頂點,所以沒有新頂點入隊。此時佇列為空,遍歷結束。
最終,廣度優先遍歷的順序即入佇列(或出佇列)的順序:A B F C G I E D H
五、完整程式碼
#include "stdio.h" #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 9 /*儲存空間初始分配量 */ #define MAXEDGE 15 #define MAXVEX 9 #define INFINITY 65535 typedef int Status;/* Status 是函式的型別,其值是函式結果狀態程式碼,如 OK 等 */ typedef int Boolean; /* Boolean 是布林型別,其值是 TRUE 或 FALSE */ typedef char VertexType; /*頂點型別應由使用者定義 */ typedef int EdgeType; /*邊上的權值型別應由使用者定義 */ typedef struct { VertexType vexs[MAXVEX]; /*頂點表 */ EdgeType arc[MAXVEX][MAXVEX];/*鄰接矩陣,可看作邊表 */ int numVertexes, numEdges; /*圖中當前的頂點數和邊數 */ }MGraph; /*用到的佇列結構與函式********************************** */ /*迴圈佇列的順序儲存結構 */ typedef struct { int data[MAXSIZE]; int front;/* 頭位置標識,相當於頭指標 */ int rear;/* 尾位置標識,相當於尾指標。若佇列不為空,指向佇列尾元素的下一個位置 */ }Queue; /*初始化一個空佇列 Q */ Status InitQueue(Queue *Q) { Q->front=0; Q->rear=0; returnOK; } /*若佇列 Q 為空佇列,則返回 TRUE,否則返回 FALSE */ Status QueueEmpty(Queue Q) { if(Q.front==Q.rear) /*佇列空的標誌 */ return TRUE; else return FALSE; } /*若佇列未滿,則插入元素 e 為 Q 新的隊尾元素 */ Status EnQueue(Queue *Q,int e) { if ((Q->rear+1)%MAXSIZE == Q->front)/*佇列滿的判斷 */ return ERROR; Q->data[Q->rear]=e; /*將元素 e 賦值給隊尾 */ Q->rear=(Q->rear+1)%MAXSIZE;/* rear 指標向後移一位置,若到最後則轉到陣列頭部 */ returnOK; } /*若佇列不為空,則刪除 Q 中隊頭元素,用e返回其值 */ Status DeQueue(Queue *Q,int *e) { if (Q->front == Q->rear)/*佇列空的判斷 */ return ERROR; *e=Q->data[Q->front];/*將隊頭元素賦值給 e */ Q->front=(Q->front+1)%MAXSIZE; /* front 指標向後移一位置,若到最後則轉到陣列頭部 */ returnOK; } void CreateMGraph(MGraph *G) { int i, j; G->numEdges=15; G->numVertexes=9; /*讀入頂點資訊,建立頂點表 */ G->vexs[0]='A'; G->vexs[1]='B'; G->vexs[2]='C'; G->vexs[3]='D'; G->vexs[4]='E'; G->vexs[5]='F'; G->vexs[6]='G'; G->vexs[7]='H'; G->vexs[8]='I'; for (i = 0; i < G->numVertexes; i++)/*初始化圖 */ { for ( j = 0; j < G->numVertexes; j++) { G->arc[i][j]=0; } } G->arc[0][1]=1; G->arc[0][5]=1; G->arc[1][2]=1; G->arc[1][6]=1; G->arc[1][8]=1; G->arc[2][3]=1; G->arc[2][8]=1; G->arc[3][4]=1; G->arc[3][6]=1; G->arc[3][7]=1; G->arc[3][8]=1; G->arc[4][5]=1; G->arc[4][7]=1; G->arc[5][6]=1; G->arc[6][7]=1; for(i = 0; i < G->numVertexes; i++) { for(j = i; j < G->numVertexes; j++) { G->arc[j][i] =G->arc[i][j]; } } } Boolean visited[MAXVEX]; /*訪問標誌的陣列 */ /*鄰接矩陣的深度優先遞迴演算法 */ void DFS(MGraph G, int i) { int j; visited[i] = TRUE; printf("%c ", G.vexs[i]);/*列印頂點,也可以其它操作 */ for(j = 0; j < G.numVertexes; j++) if(G.arc[i][j] == 1 && !visited[j]) DFS(G, j);/*對未訪問的鄰接頂點遞迴呼叫 */ } /*鄰接矩陣的深度遍歷演算法 */ void DFSTraverse(MGraph G) { int i; for(i = 0; i < G.numVertexes; i++) visited[i] = FALSE; /*初始所有頂點狀態都是未訪問過狀態 */ for(i = 0; i < G.numVertexes; i++) if(!visited[i]) /*對未訪問過的頂點呼叫 DFS,若是連通圖,只會執行一次 */ DFS(G, i); } /*鄰接矩陣的廣度遍歷演算法 */ void BFSTraverse(MGraph G) { int i, j; Queue Q; for(i = 0; i < G.numVertexes; i++) visited[i] = FALSE; InitQueue(&Q);/*初始化一輔助用的佇列 */ for(i = 0; i < G.numVertexes; i++)/*對每一個頂點做迴圈 */ { if (!visited[i])/*若是未訪問過就處理 */ { visited[i]=TRUE;/*設定當前頂點訪問過 */ printf("%c ", G.vexs[i]);/*列印頂點,也可以其它操作 */ EnQueue(&Q,i);/*將此頂點入佇列 */ while(!QueueEmpty(Q)) /*若當前佇列不為空 */ { DeQueue(&Q,&i);/*將隊對元素出佇列,賦值給 i */ for(j=0;j<G.numVertexes;j++) { /*判斷其它頂點若與當前頂點存在邊且未訪問過*/ if(G.arc[i][j] == 1 && !visited[j]) { visited[j]=TRUE;/*將找到的此頂點標記為已訪問*/ printf("%c ", G.vexs[j]);/*列印頂點 */ EnQueue(&Q,j);/*將找到的此頂點入佇列*/ } } } } } } int main(void) { MGraph G; CreateMGraph(&G); printf("\n 深度遍歷:"); DFSTraverse(G); printf("\n 廣度遍歷:"); BFSTraverse(G); return 0; }
執行結果:
深度遍歷:A B C D E F G H I 廣度遍歷:A B F C G I E D H