從圖的某一頂點出發訪遍其余頂點,且使每一個頂點僅被訪問一次,這一過程就叫做圖的遍歷
一:深度優先遍歷(鄰接矩陣實現)
(一)定義
假設給定圖G的初態是所有頂點均未曾訪問過。在G中任選一頂點v為初始出發點(源點),則深度優先遍歷可定義如下:首先訪問出發點v,並將其標記為已訪問過;然后依次從v出發搜索v的每個鄰接點w。若w未曾訪問過,則以w為新的出發點繼續進行深度優先遍歷,直至圖中所有和源點v有路徑相通的頂點(亦稱為從源點可達的頂點)均已被訪問為止。若此時圖中仍有未訪問的頂點(未連通),則另選一個尚未訪問的頂點作為新的源點重復上述過程,直至圖中所有頂點均已被訪問為止。
圖的深度優先遍歷類似於樹的前序遍歷。采用的搜索方法的特點是盡可能先對縱深方向進行搜索。這種搜索方法稱為深度優先搜索(Depth-First Search)。相應地,用此方法遍歷圖就很自然地稱之為圖的深度優先遍歷
(二)實現思路
(1)訪問頂點v; (2)從v的未被訪問的鄰接點中選取一個頂點w,從w出發進行深度優先遍歷; (3)重復上述兩步,直至圖中所有和v有路徑相通的頂點都被訪問到。
(三)遞歸實現偽代碼
(1)訪問頂點v;visited[v]=1;//算法執行前visited[n]=0 (2)w=頂點v的第一個鄰接點; (3)while(w存在) if(w未被訪問) 從頂點w出發遞歸執行該算法; w=頂點v的下一個鄰接點;
(四)非遞歸實現偽代碼
(1)棧S初始化;visited[n]=0; (2)訪問頂點v;visited[v]=1;頂點v入棧S (3)while(棧S非空) x=棧S的頂元素(不出棧); if(存在並找到未被訪問的x的鄰接點w) 訪問w;visited[w]=1; w進棧; else x出棧;
(五)代碼實現(遞歸+非遞歸)
頭文件

#pragma once #ifndef _STACK_H #define _STACK_H #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 typedef int ElemType; typedef int Status; typedef struct { ElemType data[MAXSIZE]; int top; }Stack; Status InitStack(Stack* S); Status Push(Stack* S, ElemType e); Status Pop(Stack* S, ElemType* e); Status EmptyStack(Stack S); ElemType getTop(Stack S); #endif

#pragma once #ifndef _SEARCH_H #define _SEARCH_H #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #define MAXVEX 100 //最大頂點數 #define INFINITY 0 //用0表示∞ typedef char VertexType; //頂點類型,字符型A,B,C,D... typedef int EdgeType; //邊上權值類型10,15,... typedef struct { VertexType vers[MAXVEX]; //頂點表 EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表 int numVertexes, numEdges; //圖中當前的頂點數和邊數 }MGraph; #endif
源文件

#include "stack.h" Status InitStack(Stack* S) { if (!S) return ERROR; S->top = -1; return OK; } Status Push(Stack* S, ElemType e) { if (!S) return ERROR; S->data[++S->top] = e; return OK; } Status Pop(Stack* S, ElemType* e) { if (!S || !e) return ERROR; *e = S->data[S->top--]; return OK; } Status EmptyStack(Stack S) { if (S.top != -1) return FALSE; return TRUE; } ElemType getTop(Stack S) { if (S.top == -1) return NULL; return S.data[S.top]; }
#include "01search.h" #include "stack.h" bool visited[MAXVEX]; //訪問標志的數組 void CreateMGraph(MGraph* G); //創建鄰接矩陣 void DFSTraverse(MGraph M); //深度優先遍歷前會進行初始化 void DFS(MGraph M, int i); //鄰接矩陣深度優先算法 void DFSInter(MGraph M, int i); //鄰接矩陣深度非遞歸優先算法 void showGraph(MGraph G); //顯示矩陣

void CreateMGraph(MGraph* G) { int i, j, k, w; // printf("please input number of vertex and edge:\n"); // scanf("%d,%d", &G->numVertexes, &G->numEdges); //輸入頂點數和邊數 // getchar(); //可以獲取回車符 // for (i = 0; i < G->numVertexes; i++) //讀入頂點信息,建立頂點表 // scanf("%c", &G->vers[i]); G->numVertexes = 9; G->numEdges = 15; //讀入頂點信息 G->vers[0] = 'A'; G->vers[1] = 'B'; G->vers[2] = 'C'; G->vers[3] = 'D'; G->vers[4] = 'E'; G->vers[5] = 'F'; G->vers[6] = 'G'; G->vers[7] = 'H'; G->vers[8] = 'I'; //getchar(); //可以獲取回車符 for (i = 0; i < G->numVertexes; i++) for (j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; //鄰接矩陣初始化 G->arc[0][1] = 1; G->arc[0][5] = 1; G->arc[1][2] = 1; G->arc[1][8] = 1; G->arc[1][6] = 1; G->arc[2][3] = 1; G->arc[2][8] = 1; G->arc[3][4] = 1; G->arc[3][7] = 1; G->arc[3][6] = 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 (k = 0; k < G->numVertexes; k++) //讀入numEdges條邊,建立鄰接矩陣 { for (i = k; i < G->numVertexes; i++) { G->arc[i][k] = G->arc[k][i]; //因為是無向圖,所有是對稱矩陣 } } }

void showGraph(MGraph G) { for (int i = 0; i < G.numVertexes; i++) { for (int j = 0; j < G.numVertexes; j++) printf("%2d", G.arc[i][j]); printf("\n"); } }

void DFSTraverse(MGraph M) { int i; for (i = 0; i < M.numVertexes; i++) visited[i] = false; //初始化所有頂點狀態都是未訪問過的未訪問狀態 for (i = 0; i < M.numVertexes; i++) if (!visited[i]) //對未訪問的頂點調用DFS,若是連通圖,只會調用一次 DFSInter(M, i); //DFS(M, i); }

void DFS(MGraph M, int i) //鄰接矩陣深度優先算法 { int j; visited[i] = true; //訪問后我們會一直向后退,去訪問其他結點,不會再訪問這個結點,所有我們不需要再置為false printf("%c", M.vers[i]); for (j = 0; j < M.numVertexes; j++) if (M.arc[i][j] == 1 && !visited[j]) //因為鄰接矩陣是對稱的,所以當我們訪問到下半部時,有一些結點時前面已經訪問過的,我們就不要重復了 DFS(M, j); //對未訪問過的鄰接頂點遞歸調用 }

void DFSInter(MGraph M, int i) //鄰接矩陣深度非遞歸優先算法 { int j,flag; Stack s; InitStack(&s); visited[i] = true; //訪問后我們會一直向后退,去訪問其他結點,不會再訪問這個結點,所有我們不需要再置為false printf("%c", M.vers[i]); Push(&s, i); while (!EmptyStack(s)) { i = getTop(s); for (j = 0; j < M.numVertexes; j++) if (M.arc[i][j] == 1 && !visited[j]) //因為鄰接矩陣是對稱的,所以當我們訪問到下半部時,有一些結點時前面已經訪問過的,我們就不要重復了 { visited[j] = true; printf("%c", M.vers[j]); flag = 1; Push(&s, j); break; } if (!flag) Pop(&s, &i); flag = 0; } }
int main() { MGraph MG; CreateMGraph(&MG); showGraph(MG); DFSTraverse(MG); system("pause"); return 0; }
注意:深度優先遍歷的方法不止一種,結果也有不同種,當我們使用方法一致時,結果是一樣的,無論遞歸還是迭代
(六)應用:馬踏棋盤
規則
8*8方格,將馬放在任意位置,按照馬走日,進行移動,要求一個方格進入一次,最后使得馬走遍64個方格
回溯法
一條路走到黑,碰壁了再回來一條路走。可以與遞歸很好搭配,也可以和深度優先搜索一起
哈密爾頓路徑
是指結果圖G中的每個頂點,且只經過一次的一條軌跡。如果這條軌跡是一個閉合的路徑(從起點出發不重復的遍歷所有點后仍能回到起始點),那么這條路徑叫做哈密爾頓回路
代碼實現

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <time.h> #define X 8 #define Y 8 int chess[X][Y]; //找到x,y位置的下一個可走位置,並返回,其中count代表了我們嘗試了多少次,一個有八種走法,所有count從0-7 //按照棋盤顯示的來走 int nextxy(int* x, int* y, int count) { switch (count) { case 0: if ((*x + 2) < X && (*y - 1) >= 0 && !chess[*x + 2][*y - 1]) { *x += 2; *y -= 1; return 1; } break; case 1: if ((*x + 2) < X && (*y + 1) < Y && !chess[*x + 2][*y + 1]) { *x += 2; *y += 1; return 1; } break; case 2: if ((*x + 1) < X && (*y - 2) >= 0 && !chess[*x + 1][*y - 2]) { *x += 1; *y -= 2; return 1; } break; case 3: if ((*x + 1) < X && (*y + 2) < Y && !chess[*x + 1][*y + 2]) { *x += 1; *y += 2; return 1; } break; case 4: if ((*x - 2) >= 0 && (*y - 1) >= 0 && !chess[*x - 2][*y - 1]) { *x -= 2; *y -= 1; return 1; } break; case 5: if ((*x - 2) >= 0 && (*y + 1) < Y && !chess[*x - 2][*y + 1]) { *x -= 2; *y += 1; return 1; } break; case 6: if ((*x - 1) >= 0 && (*y - 2) >= 0 && !chess[*x - 1][*y - 2]) { *x -= 1; *y -= 2; return 1; } break; case 7: if ((*x - 1) >= 0 && (*y + 2) < Y && !chess[*x - 1][*y + 2]) { *x -= 1; *y += 2; return 1; } break; default: break; } return 0; } void ShowChess() { for (int i = 0; i < X;i++) { for (int j = 0; j < Y;j++) { printf("%5d", chess[i][j]); } printf("\n"); } } //深度優先遍歷棋盤 //(x,y)為位置坐標 //第一次調用為我們輸入的參數 //之后就是遞歸進行 //tag代表我們在棋盤上走的正確步數 TravelChessBoard(int x, int y, int tag) { int flag,count=0; int x1, y1; x1 = x; y1 = y; chess[x][y] = tag; if (tag==X*Y) { //打印棋盤 ShowChess(); return 1; } flag = nextxy(&x1, &y1, count); //我們只找了其中一種走法,如果沒有找到,我們下面需要去遍歷其他走法 while (++count < 8 && !flag) { flag = nextxy(&x1, &y1, count); } //找到路徑了 while (flag) //我們對這個點周圍的所有可以走的位置都要遍歷一下 { if (TravelChessBoard(x1, y1, tag + 1)) return 1; //若是這條路走不通,我們需要繼續換下一條路,上面的count代表我們走到第幾條路 x1 = x; y1 = y; flag = nextxy(&x1, &y1, count); while (++count < 8 && !flag) { flag = nextxy(&x1, &y1, count); } } //如果這個點各個方向都不行,我們就要換點了,先將這個點置為0 if (0 == flag) { chess[x][y] = 0; //因為這里要用到原來x,y,所以我們需要保留他 } return 0; } int main() { clock_t start, end; int i, j; start = clock(); for (i = 0; i < X; i++) for (j = 0; j < Y; j++) chess[i][j] = 0; if (!TravelChessBoard(2, 0, 1)) printf("find ways failure!\n"); end = clock(); printf("time speed:%f(ms)\n", (double)(end - start)/CLOCKS_PER_SEC); system("pause"); return 0; }
二:廣度優先遍歷(鄰接矩陣)
(一)定義
圖的廣度優先遍歷BFS算法是一個分層搜索的過程,和樹的層序遍歷算法類同,它也需要一個隊列以保持遍歷過的頂點順序,以便按出隊的順序再去訪問這些頂點的鄰接頂點。
我們需要用到隊列去實現廣度優先遍歷
(二)實現思路
(1)頂點v入隊列。 (2)當隊列非空時則繼續執行,否則算法結束。 (3)出隊列取得隊頭頂點v;訪問頂點v並標記頂點v已被訪問。 (4)查找頂點v的第一個鄰接頂點col。 (5)若v的鄰接頂點col未被訪問過的,則col入隊列。 (6)繼續查找頂點v的另一個新的鄰接頂點col,轉到步驟(5)。 直到頂點v的所有未被訪問過的鄰接點處理完。轉到步驟(2)。
廣度優先遍歷圖是以頂點v為起始點,由近至遠,依次訪問和v有路徑相通而且路徑長度為1,2,……的頂點。為了使“先被訪問頂點的鄰接點”先於“后被訪問頂點的鄰接點”被訪問,需設置隊列存儲訪問的頂點。
(三)偽代碼
(1)初始化隊列Q;visited[n]=0; (2)訪問頂點v;visited[v]=1;頂點v入隊列Q; (3) while(隊列Q非空) v=隊列Q的對頭元素出隊; w=頂點v的第一個鄰接點; while(w存在) 如果w未訪問,則訪問頂點w; visited[w]=1; 頂點w入隊列Q; w=頂點v的下一個鄰接點。
(四)代碼實現

#pragma once #ifndef _QUEUE_H #define _QUEUE_H #include <stdio.h> #include <stdlib.h> #define OK 1 #define ERROR 0 #define TRUE 1 #define FALSE 0 #define MAXSIZE 100 typedef int ElemType; typedef int Status; typedef struct _qNode { ElemType data; struct _qNode* next; }QNode,*QNodePtr; typedef struct { QNodePtr front,rear; //隊頭隊尾指針 }LinkQueue; Status InitQueue(LinkQueue* Q); Status EnQueue(LinkQueue* Q, ElemType e); Status DeQueue(LinkQueue* Q, ElemType* e); Status EmptyQueue(LinkQueue Q); Status getHead(LinkQueue Q,ElemType* e); #endif

#include "queue.h" Status InitQueue(LinkQueue* Q) { if (!Q) return ERROR; Q->front = Q->rear = (QNodePtr)malloc(sizeof(QNode)); if (!Q->front) return ERROR; Q->front->next = NULL; return OK; } Status EnQueue(LinkQueue* Q, ElemType e) { //尾插法 if (!Q) return ERROR; QNodePtr q = (QNodePtr)malloc(sizeof(QNode)); if (!q) return ERROR; q->data = e; q->next = (*Q).rear->next; (*Q).rear->next = q; Q->rear = q; return OK; } Status DeQueue(LinkQueue* Q, ElemType* e) { QNodePtr q; if (!Q || !e || EmptyQueue(*Q)) return ERROR; q = Q->front->next; Q->front->next = q->next; *e = q->data; if (Q->rear == q) Q->rear = Q->front; free(q); return OK; } Status EmptyQueue(LinkQueue Q) { if (!Q.front->next) return TRUE; return FALSE; } Status getHead(LinkQueue Q,ElemType* e) { QNodePtr q; if (EmptyQueue(Q)) return ERROR; q = Q.front->next; *e = q->data; return OK; }

void BFSTraverse(MGraph G) { LinkQueue Q; int i,j,k; 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.vers[i]); //打印頂點 EnQueue(&Q, i); //將此頂點入隊 while (!EmptyQueue(Q)) //若當前頂點不為空 { DeQueue(&Q, &k); //出隊,獲取出隊行的相關列 for (j = 0; j < G.numVertexes;j++) { if (G.arc[k][j]==1&&!visited[j]) //若是該列未被訪問 { visited[j] = true; //標記訪問過 printf("%c", G.vers[j]); //輸出數據 EnQueue(&Q, j); //將其入隊 } } } } } }
三:鄰接表實現深度優先和廣度優先
隊列和棧的實現代碼如上面一致

#pragma once #ifndef _SEARCH_H #define _SEARCH_H #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdbool.h> #define MAXVEX 100 //最大頂點數 #define INFINITY 0 //用0表示∞ typedef char VertexType; //頂點類型,字符型A,B,C,D... typedef int EdgeType; //邊上權值類型10,15,... //鄰接矩陣結構 typedef struct { VertexType vers[MAXVEX]; //頂點表 EdgeType arc[MAXVEX][MAXVEX]; //鄰接矩陣,可看作邊表 int numVertexes, numEdges; //圖中當前的頂點數和邊數 }MGraph; //鄰接表結構 //邊表結點 typedef struct _EdgeNode { int adjvex; //存放鄰接點 struct _EdgeNode* next; //指向下一個 }EdgeNode; //頂點表結點 typedef struct _VertexNode { VertexType data; //頂點域,存儲頂點信息 EdgeNode* fisrtedge; //邊表頭指針 }VertexNode, AdjList[MAXVEX]; //鄰接表指針 typedef struct { AdjList adjList; //鄰接表數組 int numVertexes, numEdges; //圖中所存儲的頂點數和邊表數 }GraphAdjList; #endif
#include "01search.h" #include "stack.h" #include "queue.h" bool visited[MAXVEX]; //訪問標志的數組 void CreateMGraph(MGraph* G); void CreateALGraph(MGraph G, GraphAdjList *GL);//利用鄰接矩陣構建鄰接表 void DFSTraverse(GraphAdjList M); void DFS(GraphAdjList G, int i); //鄰接矩陣深度優先算法 void DFSInter(GraphAdjList G, int i); //鄰接矩陣深度非遞歸優先算法 void BFSTraverse(GraphAdjList G); //廣度優先遍歷 void showGraph(MGraph G); //顯示鄰接矩陣 void ShowGraphAdjList(GraphAdjList GL); //顯示鄰接表 int main() { MGraph MG; GraphAdjList GL; CreateMGraph(&MG); showGraph(MG); CreateALGraph(MG, &GL); ShowGraphAdjList(GL); DFSTraverse(GL); BFSTraverse(GL); system("pause"); return 0; } void CreateMGraph(MGraph* G) { int i, j, k, w; // printf("please input number of vertex and edge:\n"); // scanf("%d,%d", &G->numVertexes, &G->numEdges); //輸入頂點數和邊數 // getchar(); //可以獲取回車符 // for (i = 0; i < G->numVertexes; i++) //讀入頂點信息,建立頂點表 // scanf("%c", &G->vers[i]); G->numVertexes = 9; G->numEdges = 15; //讀入頂點信息 G->vers[0] = 'A'; G->vers[1] = 'B'; G->vers[2] = 'C'; G->vers[3] = 'D'; G->vers[4] = 'E'; G->vers[5] = 'F'; G->vers[6] = 'G'; G->vers[7] = 'H'; G->vers[8] = 'I'; //getchar(); //可以獲取回車符 for (i = 0; i < G->numVertexes; i++) for (j = 0; j < G->numVertexes; j++) G->arc[i][j] = INFINITY; //鄰接矩陣初始化 G->arc[0][1] = 1; G->arc[0][5] = 1; G->arc[1][2] = 1; G->arc[1][8] = 1; G->arc[1][6] = 1; G->arc[2][3] = 1; G->arc[2][8] = 1; G->arc[3][4] = 1; G->arc[3][7] = 1; G->arc[3][6] = 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 (k = 0; k < G->numVertexes; k++) //讀入numEdges條邊,建立鄰接矩陣 { for (i = k; i < G->numVertexes; i++) { G->arc[i][k] = G->arc[k][i]; //因為是無向圖,所有是對稱矩陣 } } } //利用鄰接矩陣構建鄰接表 void CreateALGraph(MGraph G, GraphAdjList *GL) { EdgeNode* e; int i,j; //設置頂點數和邊表數 GL->numEdges = G.numEdges; GL->numVertexes = G.numVertexes; //建立頂點表 for (i = 0; i < G.numVertexes;i++) { GL->adjList[i].data = G.vers[i]; GL->adjList[i].fisrtedge = NULL; } //建立邊表 for (i = 0; i < G.numVertexes;i++) { for (j = 0; j < G.numVertexes;j++) { if (G.arc[i][j]==1) { e = (EdgeNode*)malloc(sizeof(EdgeNode)); e->adjvex = j; e->next = GL->adjList[i].fisrtedge; GL->adjList[i].fisrtedge = e; } } } } void DFSTraverse(GraphAdjList G) { int i; for (i = 0; i < G.numVertexes; i++) visited[i] = false; //初始化所有頂點狀態都是未訪問過的未訪問狀態 for (i = 0; i < G.numVertexes; i++) if (!visited[i]) //對未訪問的頂點調用DFS,若是連通圖,只會調用一次 //DFSInter(G, i); DFS(G, i); printf("\n"); } void DFS(GraphAdjList G, int i) //鄰接矩陣深度優先算法 { int j; EdgeNode* p; visited[i] = true; //訪問后我們會一直向后退,去訪問其他結點,不會再訪問這個結點,所有我們不需要再置為false printf("%c", G.adjList[i].data); //打印頂點 p = G.adjList[i].fisrtedge; while (p) { if (!visited[p->adjvex]) DFS(G, p->adjvex); p = p->next; } } void DFSInter(GraphAdjList G, int i) //鄰接矩陣深度非遞歸優先算法 { int j, flag; Stack s; EdgeNode* e; InitStack(&s); visited[i] = true; //訪問后我們會一直向后退,去訪問其他結點,不會再訪問這個結點,所有我們不需要再置為false printf("%c", G.adjList[i].data); Push(&s, i); while (!EmptyStack(s)) { i = getTop(s); e = G.adjList[i].fisrtedge; while (e) { if (!visited[e->adjvex]) { printf("%c", G.adjList[e->adjvex].data); visited[e->adjvex] = true; flag = 1; Push(&s, e->adjvex); break; } e = e->next; } if (!flag) Pop(&s, &i); flag = 0; } } void BFSTraverse(GraphAdjList G) { LinkQueue Q; int i, j, k; EdgeNode* 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.adjList[i].data); //打印頂點 EnQueue(&Q, i); //將此頂點入隊 while (!EmptyQueue(Q)) //若當前頂點不為空 { DeQueue(&Q, &k); //出隊,獲取出隊行的相關列 q = G.adjList[k].fisrtedge; while (q) { if (!visited[q->adjvex]) { visited[q->adjvex] = true; //標記訪問過 printf("%c", G.adjList[q->adjvex].data); //輸出數據 EnQueue(&Q, q->adjvex); //將其入隊 } q = q->next; } } } } printf("\n"); } void showGraph(MGraph G) { for (int i = 0; i < G.numVertexes; i++) { for (int j = 0; j < G.numVertexes; j++) printf("%2d", G.arc[i][j]); printf("\n"); } } void ShowGraphAdjList(GraphAdjList GL) { EdgeNode* e; for (int i = 0; i < GL.numVertexes;i++) { printf("%d: ", i); e = GL.adjList[i].fisrtedge; while (e) { printf("%5d", e->adjvex); e = e->next; } printf("\n"); } }