圖 | 兩種遍歷方式:深度優先搜索(DFS、深搜)和廣度優先搜索(BFS、廣搜)

前邊介紹了有關圖的 4 種存儲方式,本節介紹如何對存儲的圖中的頂點進行遍歷。常用的遍歷方式有兩種:深度優先搜索廣度優先搜索

深度優先搜索(簡稱“深搜”或DFS)


圖 1 無向圖

深度優先搜索的過程類似於樹的先序遍歷,首先從例子中體會深度優先搜索。例如圖 1 是一個無向圖,採用深度優先算法遍歷這個圖的過程爲:

  1. 首先任意找一個未被遍歷過的頂點,例如從 V1 開始,由於 V1 率先訪問過了,所以,需要標記 V1 的狀態爲訪問過;
  2. 然後遍歷 V1 的鄰接點,例如訪問 V2 ,並做標記,然後訪問 V2 的鄰接點,例如 V4 (做標記),然後 V8 ,然後 V5 ;
  3. 當繼續遍歷 V5 的鄰接點時,根據之前做的標記顯示,所有鄰接點都被訪問過了。此時,從 V5 回退到 V8 ,看 V8 是否有未被訪問過的鄰接點,如果沒有,繼續回退到 V4 , V2 , V1 ;
  4. 通過查看 V1 ,找到一個未被訪問過的頂點 V3 ,繼續遍歷,然後訪問 V3 鄰接點 V6 ,然後 V7 ;
  5. 由於 V7 沒有未被訪問的鄰接點,所有回退到 V6 ,繼續回退至 V3 ,最後到達 V1 ,發現沒有未被訪問的;
  6. 最後一步需要判斷是否所有頂點都被訪問,如果還有沒被訪問的,以未被訪問的頂點爲第一個頂點,繼續依照上邊的方式進行遍歷。

根據上邊的過程,可以得到圖 1 通過深度優先搜索獲得的頂點的遍歷次序爲:V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7

所謂深度優先搜索,是從圖中的一個頂點出發,每次遍歷當前訪問頂點的鄰接點,一直到訪問的頂點沒有未被訪問過的鄰接點爲止。然後採用依次回退的方式,查看來的路上每一個頂點是否有其它未被訪問的鄰接點。訪問完成後,判斷圖中的頂點是否已經全部遍歷完成,如果沒有,以未訪問的頂點爲起始點,重複上述過程。

深度優先搜索是一個不斷回溯的過程

採用深度優先搜索算法遍歷圖的實現代碼爲(鄰接矩陣+無向圖(有向圖也是如此)):

#include <stdio.h>

#define MAX_VERtEX_NUM 20                   //頂點的最大個數
#define VRType int                          //表示頂點之間的關係的變量類型
#define InfoType char                       //存儲弧或者邊額外信息的指針變量類型
#define VertexType int                      //圖中頂點的數據類型

typedef enum{false,true}bool;               //定義bool型常量
bool visited[MAX_VERtEX_NUM];               //設置全局數組,記錄標記頂點是否被訪問過

typedef struct {
    VRType adj;                             //對於無權圖,用 1 或 0 表示是否相鄰;對於帶權圖,直接爲權值。
    InfoType * info;                        //弧或邊額外含有的信息指針
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];

typedef struct {
    VertexType vexs[MAX_VERtEX_NUM];        //存儲圖中頂點數據
    AdjMatrix arcs;                         //二維數組,記錄頂點之間的關係
    int vexnum,arcnum;                      //記錄圖的頂點數和弧(邊)數
}MGraph;

//根據頂點本身數據,判斷出頂點在二維數組中的位置
int LocateVex(MGraph * G,VertexType v){
    int i=0;
    //遍歷一維數組,找到變量v
    for (; i<G->vexnum; i++) {
        if (G->vexs[i]==v) {
            break;
        }
    }
    //如果找不到,輸出提示語句,返回-1
    if (i>G->vexnum) {
        printf("no such vertex.\n");
        return -1;
    }
    return i;
}

//構造無向圖
void CreateDN(MGraph *G){
    scanf("%d,%d",&(G->vexnum),&(G->arcnum));
    for (int i=0; i<G->vexnum; i++) {
        scanf("%d",&(G->vexs[i]));
    }
    for (int i=0; i<G->vexnum; i++) {
        for (int j=0; j<G->vexnum; j++) {
            G->arcs[i][j].adj=0;
            G->arcs[i][j].info=NULL;
        }
    }
    for (int i=0; i<G->arcnum; i++) {
        int v1,v2;
        scanf("%d,%d",&v1,&v2);
        int n=LocateVex(G, v1);
        int m=LocateVex(G, v2);
        if (m==-1 ||n==-1) {
            printf("no this vertex\n");
            return;
        }
        G->arcs[n][m].adj=1;
        G->arcs[m][n].adj=1;//無向圖的二階矩陣沿主對角線對稱
    }
}

int FirstAdjVex(MGraph G,int v)
{
    //查找與數組下標爲v的頂點之間有邊的頂點,返回它在數組中的下標
    for(int i = 0; i<G.vexnum; i++){
        if( G.arcs[v][i].adj ){
            return i;
        }
    }
    return -1;
}

int NextAdjVex(MGraph G,int v,int w)
{
    //從前一個訪問位置w的下一個位置開始,查找之間有邊的頂點
    for(int i = w+1; i<G.vexnum; i++){
        if(G.arcs[v][i].adj){
            return i;
        }
    }
    return -1;
}

void visitVex(MGraph G, int v){
    printf("%d ",G.vexs[v]);
}

void DFS(MGraph G,int v){
    visited[v] = true;//標記爲true
    visitVex( G,  v); //訪問第v 個頂點
    //從該頂點的第一個邊開始,一直到最後一個邊,對處於邊另一端的頂點調用DFS函數
    for(int w = FirstAdjVex(G,v); w>=0; w = NextAdjVex(G,v,w)){
        //如果該頂點的標記位false,證明未被訪問,調用深度優先搜索函數
        if(!visited[w]){
            DFS(G,w);
        }
    }
}

//深度優先搜索
void DFSTraverse(MGraph G){//
    int v;
    //將用做標記的visit數組初始化爲false
    for( v = 0; v < G.vexnum; ++v){
        visited[v] = false;
    }
    //對於每個標記爲false的頂點調用深度優先搜索函數
    for( v = 0; v < G.vexnum; v++){
        //如果該頂點的標記位爲false,則調用深度優先搜索函數
        if(!visited[v]){
            DFS( G, v);
        }
    }
}

int main() {
    MGraph G;//建立一個圖的變量
    CreateDN(&G);//初始化圖
    DFSTraverse(G);//深度優先搜索圖
    return 0;
}

以圖 1 爲例,運行結果爲:

8,9
1
2
3
4
5
6
7
8
1,2
2,4
2,5
4,8
5,8
1,3
3,6
6,7
7,3
1 2 4 8 5 3 6 7

廣度優先搜索


廣度優先搜索類似於樹的層次遍歷。從圖中的某一頂點出發,遍歷每一個頂點時,依次遍歷其所有的鄰接點,然後再從這些鄰接點出發,同樣依次訪問它們的鄰接點。按照此過程,直到圖中所有被訪問過的頂點的鄰接點都被訪問到。

最後還需要做的操作就是查看圖中是否存在尚未被訪問的頂點,若有,則以該頂點爲起始點,重複上述遍歷的過程。

還拿圖 1 中的無向圖爲例:

  1. 假設 V1 作爲起始點,遍歷其所有的鄰接點 V2 和 V3;
  2. 然後以 V2 爲起始點,訪問鄰接點 V4 和 V5 ;
  3. 然後以 V3 爲起始點,訪問鄰接點 V6 、 V7 ;
  4. 然後以 V4 爲起始點訪問 V8 ;
  5. 然後以 V5 爲起始點,由於 V5 所有的起始點已經全部被訪問,所有直接略過;
  6. 然後V6 和 V7 也是如此。
  7. 最後,以 V1 爲起始點的遍歷過程結束後,還需判斷圖中是否還有未被訪問的點,由於圖 1 中沒有了,所以整個圖遍歷結束。如果還有沒被訪問的,以未被訪問的頂點爲第一個頂點,繼續依照上邊的方式進行遍歷。

根據上邊的過程,可以得到圖 1 通過廣度優先搜索獲得的頂點的遍歷次序爲:V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8

廣度優先搜索的實現需要藉助隊列這一特殊數據結構,實現代碼爲(鄰接矩陣+無向圖(有向圖也是如此))::

#include <stdio.h>
#include <stdlib.h>
#define MAX_VERtEX_NUM 20                   //頂點的最大個數
#define VRType int                          //表示頂點之間的關係的變量類型
#define InfoType char                       //存儲弧或者邊額外信息的指針變量類型
#define VertexType int                      //圖中頂點的數據類型
typedef enum{false,true}bool;               //定義bool型常量
bool visited[MAX_VERtEX_NUM];               //設置全局數組,記錄標記頂點是否被訪問過

typedef struct Queue{
    VertexType data;
    struct Queue * next;
}Queue;

typedef struct {
    VRType adj;                             //對於無權圖,用 1 或 0 表示是否相鄰;對於帶權圖,直接爲權值。
    InfoType * info;                        //弧或邊額外含有的信息指針
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];

typedef struct {
    VertexType vexs[MAX_VERtEX_NUM];        //存儲圖中頂點數據
    AdjMatrix arcs;                         //二維數組,記錄頂點之間的關係
    int vexnum,arcnum;                      //記錄圖的頂點數和弧(邊)數
}MGraph;

//根據頂點本身數據,判斷出頂點在二維數組中的位置
int LocateVex(MGraph * G,VertexType v){
    int i=0;
    //遍歷一維數組,找到變量v
    for (; i<G->vexnum; i++) {
        if (G->vexs[i]==v) {
            break;
        }
    }
    //如果找不到,輸出提示語句,返回-1
    if (i>G->vexnum) {
        printf("no such vertex.\n");
        return -1;
    }
    return i;
}

//構造無向圖
void CreateDN(MGraph *G){
    scanf("%d,%d",&(G->vexnum),&(G->arcnum));
    for (int i=0; i<G->vexnum; i++) {
        scanf("%d",&(G->vexs[i]));
    }
    for (int i=0; i<G->vexnum; i++) {
        for (int j=0; j<G->vexnum; j++) {
            G->arcs[i][j].adj=0;
            G->arcs[i][j].info=NULL;
        }
    }
    for (int i=0; i<G->arcnum; i++) {
        int v1,v2;
        scanf("%d,%d",&v1,&v2);
        int n=LocateVex(G, v1);
        int m=LocateVex(G, v2);
        if (m==-1 ||n==-1) {
            printf("no this vertex\n");
            return;
        }
        G->arcs[n][m].adj=1;
        G->arcs[m][n].adj=1;//無向圖的二階矩陣沿主對角線對稱
    }
}

int FirstAdjVex(MGraph G,int v)
{
    //查找與數組下標爲v的頂點之間有邊的頂點,返回它在數組中的下標
    for(int i = 0; i<G.vexnum; i++){
        if( G.arcs[v][i].adj ){
            return i;
        }
    }
    return -1;
}

int NextAdjVex(MGraph G,int v,int w)
{
    //從前一個訪問位置w的下一個位置開始,查找之間有邊的頂點
    for(int i = w+1; i<G.vexnum; i++){
        if(G.arcs[v][i].adj){
            return i;
        }
    }
    return -1;
}

//操作頂點的函數
void visitVex(MGraph G, int v){
    printf("%d ",G.vexs[v]);
}

//初始化隊列
void InitQueue(Queue ** Q){
    (*Q)=(Queue*)malloc(sizeof(Queue));
    (*Q)->next=NULL;
}

//頂點元素v進隊列
void EnQueue(Queue **Q,VertexType v){
    Queue * element=(Queue*)malloc(sizeof(Queue));
    element->data=v;
    Queue * temp=(*Q);
    while (temp->next!=NULL) {
        temp=temp->next;
    }
    temp->next=element;
}

//隊頭元素出隊列
void DeQueue(Queue **Q,int *u){
    (*u)=(*Q)->next->data;
    (*Q)->next=(*Q)->next->next;
}

//判斷隊列是否爲空
bool QueueEmpty(Queue *Q){
    if (Q->next==NULL) {
        return true;
    }
    return false;
}

//廣度優先搜索
void BFSTraverse(MGraph G){//
    int v;
    //將用做標記的visit數組初始化爲false
    for( v = 0; v < G.vexnum; ++v){
        visited[v] = false;
    }
    //對於每個標記爲false的頂點調用深度優先搜索函數
    Queue * Q;
    InitQueue(&Q);
    for( v = 0; v < G.vexnum; v++){
        if(!visited[v]){
            visited[v]=true;
            visitVex(G, v);
            EnQueue(&Q, G.vexs[v]);
            while (!QueueEmpty(Q)) {
                int u;
                DeQueue(&Q, &u);
                u=LocateVex(&G, u);
                for (int w=FirstAdjVex(G, u); w>=0; w=NextAdjVex(G, u, w)) {
                    if (!visited[w]) {
                        visited[w]=true;
                        visitVex(G, w);
                        EnQueue(&Q, G.vexs[w]);
                    }
                }
            }
        }
    }
}

int main() {
    MGraph G;//建立一個圖的變量
    CreateDN(&G);//初始化圖
    BFSTraverse(G);//廣度優先搜索圖
    return 0;
}

例如,使用上述程序代碼遍歷圖 1 中的無向圖,運行結果爲:

8,9
1
2
3
4
5
6
7
8
1,2
2,4
2,5
4,8
5,8
1,3
3,6
6,7
7,3
1 2 3 4 5 6 7 8

總結


本節介紹了兩種遍歷圖的方式:深度優先搜索算法和廣度優先搜索算法。深度優先搜索算法的實現運用的主要是回溯法,類似於樹的先序遍歷算法。廣度優先搜索算法藉助隊列的先進先出的特點,類似於樹的層次遍歷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章