圖的遍歷之深度優先遍歷與廣度優先遍歷

一、簡介

1、對於樹而言,因爲根結點只有一個,並且所有的結點都只有一個雙親,所以對於樹的遍歷相對容易一點。但是對於圖的遍歷,就不那麼容易了,因爲它的任一頂點都可以和其餘的所有頂點相鄰接,因此極有可能存在重複走過某個頂點或漏了某個頂點的遍歷過程。對於圖的遍歷,通常有兩種遍歷次序方案,分別是深度優先遍歷和廣度優先遍歷。

二、深度優先遍歷

1、深度優先遍歷(DepthFirstSearch),也稱爲深度優先搜索,簡稱爲DFS。它的思想簡單來說就是按照一定的順序挨個查找,找完一個,繼續找下一個。

2、如下圖所示,假如從頂點A開始,該如何遍歷各個頂點?可以根絕右手原則:在沒有碰到重複頂點的情況下,分叉路口始終是向右手邊走,每路過一個頂點就做一個記號。


  那麼現在從頂點A開始,依據右手原則,那麼開始遍歷頂點B,依據右手原則遍歷頂點C,依據右手原則遍歷頂點D,依據右手原則遍歷頂點E,依據右手原則遍歷頂點F,然後發現頂點A已經被標記過了,依據右手原則已行不通,那麼只能遍歷頂點G,然後發現頂點B也已經被標記過了,且頂點D也被標記過了,那麼只能遍歷頂點H,然後發現頂點D和E也都被標記過了,前方已經沒有頂點可被遍歷了,那麼回退到頂點G,也是已經沒有可遍歷的頂點了,繼續回退到頂點F,繼續回退到頂點E,頂點D,然後發現頂點I沒遍歷,那麼遍歷頂點I,然後沒有可遍歷的頂點了,回退到頂點D,繼續回退到頂點C、B,最後回退到頂點A,那麼遍歷完畢。

3、從上面的遍歷過程來看,深度優先遍歷其實就是一個遞歸的過程。而且整個遍歷過程就像是一棵樹的前序遍歷。如下圖,藍色的路線就是上面遍歷的一個順序,紅色頂點就是在各個分叉路徑的選擇項,但是根據右手原則,是不會選擇遍歷它們的;那麼對於藍色線路這棵樹來說就是對該樹的前序遍歷。


4、對於無向圖採用鄰接矩陣存儲方式的深度優先遍歷完整代碼如下:

/***************************************************************************/
/**               圖的遍歷之深度優先遍歷(鄰接矩陣存儲)                         **/
/***************************************************************************/

#include <stdio.h>

#define GRAPH_MAX_VERTEX_SIZE 100           // 最大頂點數
#define TRUE 1
#define FALSE 0

typedef char VertexType;     // 頂點類型
typedef int EdgeType;        // 代表是否有邊的類型值
typedef int Boolean;

Boolean visited[GRAPH_MAX_VERTEX_SIZE];    // 遍歷標誌數組

typedef struct Graph
{
	VertexType vexs[GRAPH_MAX_VERTEX_SIZE];           // 頂點表
	EdgeType arc[GRAPH_MAX_VERTEX_SIZE][GRAPH_MAX_VERTEX_SIZE];      // 鄰接矩陣
	int numVertexs, numEdges;            // 圖中當前的頂點數和邊數
}Graph;

/**
 * 建立無向圖的鄰接矩陣表示
 * @param graph:指向圖結構的指針
 */
void CreateGraph(Graph *graph)
{
	int i, j, k;

	printf("輸入頂點數和邊數,分別用空格分隔:");
	scanf("%d %d", &(graph->numVertexs), &(graph->numEdges));          // 接收輸入的頂點數和邊數

	for(i = 0; i < graph->numVertexs; i++)                             // 讀入頂點信息,建立頂點表
	{
	    printf("輸入第%d個頂點信息:", i + 1);
        fflush(stdin);                                                 // 清空鍵盤輸入緩衝區
		scanf("%c", &(graph->vexs[i]));
	}

	for(i = 0; i < graph->numVertexs; i++)
	{
		for(j = 0; j < graph->numVertexs; j++)
		{
			graph->arc[i][j] = 0;	                           // 鄰接矩陣初始化
		}
	}

	for(k = 0; k < graph->numEdges; k++)                               // 讀入numEdges條邊,建立鄰接矩陣
	{
		printf("輸入邊(vi,vj)上的下標i,下標j,分別用空格分隔:");
		fflush(stdin);                                                 // 清空鍵盤輸入緩衝區
		scanf("%d %d", &i, &j);
		graph->arc[i][j] = 1;
		graph->arc[j][i] = graph->arc[i][j];                           // 因爲是無向圖,矩陣對稱
	}
}
/**
 * 鄰接矩陣的深度優先遍歷遞歸算法
 * @param graph:圖結構
 * @param i:遍歷到第i個頂點
 */
void DFS(Graph graph, int i)
{
    int j;
    visited[i] = TRUE;                   // 標誌已經被遍歷過了
    printf("%c ", graph.vexs[i]);

    for(j = 0; j < graph.numVertexs; j++)
    {
        // 如果與第i個頂點相鄰接且該頂點還沒有被遍歷過,則對鄰接頂點遞歸調用
        if(graph.arc[i][j] == 1 && !visited[j])
        {
            DFS(graph, j);
        }
    }
}

/**
 * 鄰接矩陣的深度優先遍歷操作
 * @param graph:圖結構
 */
void DFSTraverse(Graph graph)
{
    int i;
    for(i = 0; i < graph.numVertexs; i++)
    {
        // 初始所有頂點都是未被遍歷過的狀態
        visited[i] = FALSE;
    }
    for(i = 0; i < graph.numVertexs; i++)
    {
        // 對未訪問過的頂點調用DFS,若是連通圖,只會執行一次
        if(!visited[i])
        {
            DFS(graph, i);
        }
    }
}

int main()
{
    Graph graph;

    CreateGraph(&graph);
    DFSTraverse(graph);
    return 0;
}
5、對於無向圖採用鄰接表的存儲方式的深度優先遍歷完整代碼如下:

/***************************************************************************/
/**                 圖的遍歷之深度優先遍歷(鄰接表存儲)                         **/
/***************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define GRAPH_MAX_VERTEX_SIZE 100                    // 最大頂點數
#define TRUE 1
#define FALSE 0

typedef char VertexType;                             // 頂點類型
typedef int Boolean;

Boolean visited[GRAPH_MAX_VERTEX_SIZE];              // 頂點訪問標誌數組

// 邊表結點結構定義
typedef struct EdgeNode
{
	int adjvex;                                      // 鄰接點域,存儲該頂點在頂點表中對應的下標
	struct EdgeNode *next;                           // 鏈表指針域,指向下一個鄰接點
}EdgeNode;

// 頂點表結點結構定義
typedef struct VertexNode
{
	VertexType data;                                 // 頂點域,存儲頂點信息
	EdgeNode *firstEdge;                             // 邊表頭指針
}VertexNode;

// 圖結構定義
typedef struct Graph
{
	VertexNode adjList[GRAPH_MAX_VERTEX_SIZE];
	int numVertexs, numEdges;                        // 圖中當前頂點數和邊數
}Graph;

/**
 * 建立無向圖的鄰接表表示
 * @param graph:指向圖結構的指針
 */
void CreateGraph(Graph *graph)
{
    int i, j, k;
    EdgeNode *e;

    printf("輸入無向圖的頂點數和邊數,分別用空格分隔:");
    scanf("%d %d", &(graph->numVertexs), &(graph->numEdges));  // 接收輸入的頂點數和邊數,並賦值

    // 讀入頂點信息,建立頂點表
    for(i = 0; i < graph->numVertexs; i++)
    {
        printf("輸入第%d個頂點信息:", i + 1);
        fflush(stdin);             // 清空鍵盤輸入緩衝區
        scanf("%c", &(graph->adjList[i].data));
        graph->adjList[i].firstEdge = NULL;     // 將該頂點指向第一個邊表結點的指針置爲空
    }

    // 建立邊表
    for(k = 0; k < graph->numEdges; k++)
    {
        printf("輸入邊(vi, vj)的兩個頂點在頂點數組中的下標i、下標j,分別用空格分隔:");
        fflush(stdin);           // 清空鍵盤輸入緩衝區
        scanf("%d %d", &i, &j);
        e = (EdgeNode *)malloc(sizeof(EdgeNode));  // 生成邊表結點
        if(!e)
        {
            exit(1);
        }
        e->adjvex = j;     // 鄰接序號爲j
        e->next = graph->adjList[i].firstEdge;   // 將邊表結點e的next指針指向當前頂點上firstEdge指針指向的邊表結點
        graph->adjList[i].firstEdge = e;         // 將當前頂點的firstEdge指針指向邊表結點e,頭插法

        // 因爲是無向圖,所以需要將(vi, vj)表示的邊反過來再執行一遍,設置下標爲j的頂點的邊表
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        if(!e)
        {
            exit(1);
        }
        e->adjvex = i;
        e->next = graph->adjList[j].firstEdge;
        graph->adjList[j].firstEdge = e;
    }
}

/**
 * 鄰接表的深度優先遍歷遞歸算法
 * @param graph:圖結構
 * @param i:遍歷到第i個頂點
 */
void DFS(Graph graph, int i)
{
    EdgeNode *e;
    visited[i] = TRUE;                    // 標誌該頂點已經被訪問過了
    printf("%c ", graph.adjList[i].data);

    e = graph.adjList[i].firstEdge;
    while(e)
    {
        if(!visited[e->adjvex])
        {
            DFS(graph, e->adjvex);    // 對訪問的鄰接頂點進行遞歸調用
        }
        e = e->next;
    }
}

/**
 * 鄰接表的深度優先遍歷操作
 * @param graph:圖結構
 */
void DFSTraverse(Graph graph)
{
    int i;

    // 初始化頂點訪問標誌數組,所有頂點都還沒有被訪問過
    for(i = 0; i < graph.numVertexs; i++)
    {
        visited[i] = FALSE;
    }
    for(i = 0; i < graph.numVertexs; i++)
    {
        // 對未訪問過的頂點調用DFS,若是連通圖,只會執行一次
        if(!visited[i])
        {
            DFS(graph, i);
        }
    }
}
int main()
{
    Graph graph;

    CreateGraph(&graph);
    DFSTraverse(graph);
    return 0;
}

三、廣度優先遍歷

1、廣度優先遍歷(BreadthFirstSearch),又稱爲廣度優先搜索,簡稱BFS。

2、如果以找鑰匙的例子來講,運用深度優先遍歷意味着要先徹底查找完一個房間再開始另一個房間的搜索。但可能鑰匙放在沙發地下等犄角旮旯的可能性極低,因此運用新的方案:先看看鑰匙是否放在各個房間的顯眼位置,如果沒有,再看看各個房間的抽屜有沒有。這樣逐步擴大查找的範圍的方式稱爲廣度優先遍歷。如下圖:廣度優先遍歷類似於樹的層序遍歷。


3、要實現對圖的廣度遍歷,可以利用隊列來實現。如下圖,出隊列的順序就是廣度優先遍歷的順序。


4、對於無向圖採用鄰接矩陣存儲方式的廣度優先遍歷完整代碼如下:

/***************************************************************************/
/**               圖的遍歷之廣度優先遍歷(鄰接矩陣存儲)                        **/
/***************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define GRAPH_MAX_VERTEX_SIZE 100           // 圖的最大頂點數
#define TRUE 1
#define FALSE 0

typedef char VertexType;     // 圖的頂點類型
typedef int QueueElemType;   // 隊列元素數據類型
typedef int EdgeType;        // 代表是否有邊的類型值
typedef int Boolean;

Boolean visited[GRAPH_MAX_VERTEX_SIZE];    // 遍歷標誌數組

// 定義圖結構
typedef struct Graph
{
	VertexType vexs[GRAPH_MAX_VERTEX_SIZE];           // 頂點表
	EdgeType arc[GRAPH_MAX_VERTEX_SIZE][GRAPH_MAX_VERTEX_SIZE];      // 鄰接矩陣
	int numVertexs, numEdges;            // 圖中當前的頂點數和邊數
}Graph;

// 定義隊列元素結構
typedef struct QueueNode
{
    QueueElemType data;
    struct QueueNode *next;
}QueueNode;

// 定義隊列結構
typedef struct
{
    QueueNode *front;  // 對頭指針
    QueueNode *rear;   // 對尾指針
}LinkQueue;

/**
 * 初始化一個空隊列,初始化成功返回TRUE,否則返回FALSE
 * @param queue:指向隊列結構的指針
 */
Boolean initLinkQueue(LinkQueue *queue)
{
    QueueNode *head;

    // 生成頭結點
    head = (QueueNode *)malloc(sizeof(QueueNode));

    if(!head)
    {
        return FALSE;
    }

    head->next = NULL;
    queue->front = queue->rear = head;
    return TRUE;
}

/**
 * 入隊列,入隊列成功返回TRUE,否則返回FALSE
 * @param queue:指向隊列結構的指針
 * @param data:入隊列元素
 */
Boolean insertLinkQueue(LinkQueue *queue, QueueElemType data)
{
    QueueNode *p;

    p = (QueueNode *)malloc(sizeof(QueueNode));
    if(!p)
    {
        return FALSE;
    }
    p->data = data;
    p->next = NULL;

    queue->rear->next = p;
    queue->rear = p;

    return TRUE;
}

/**
 * 出隊列,空隊列的情況下出隊列返回FALSE,否則返回TRUE
 * @param queue:指向隊列結構的指針
 * @param data:保存成功出隊列的元素
 */
Boolean deleteLinkQueue(LinkQueue *queue, QueueElemType *data)
{
    QueueNode *p;

    if(queue->front == queue->rear)
    {
        return FALSE;
    }

    p = queue->front->next;
    *data = p->data;

    queue->front->next = p->next;
    if(p == queue->rear)
    {
        queue->rear = queue->front;
    }
    free(p);
    return TRUE;
}

/**
 * 建立無向圖的鄰接矩陣表示
 * @param graph:指向圖結構的指針
 */
void CreateGraph(Graph *graph)
{
	int i, j, k;

	printf("輸入頂點數和邊數,分別用空格分隔:");
	scanf("%d %d", &(graph->numVertexs), &(graph->numEdges));          // 接收輸入的頂點數和邊數

	for(i = 0; i < graph->numVertexs; i++)                             // 讀入頂點信息,建立頂點表
	{
	    printf("輸入第%d個頂點信息:", i + 1);
        fflush(stdin);                                                 // 清空鍵盤輸入緩衝區
		scanf("%c", &(graph->vexs[i]));
	}

	for(i = 0; i < graph->numVertexs; i++)
	{
		for(j = 0; j < graph->numVertexs; j++)
		{
			graph->arc[i][j] = 0;	                           // 鄰接矩陣初始化
		}
	}

	for(k = 0; k < graph->numEdges; k++)                               // 讀入numEdges條邊,建立鄰接矩陣
	{
		printf("輸入邊(vi,vj)上的下標i,下標j,分別用空格分隔:");
		fflush(stdin);                                                 // 清空鍵盤輸入緩衝區
		scanf("%d %d", &i, &j);
		graph->arc[i][j] = 1;
		graph->arc[j][i] = graph->arc[i][j];                           // 因爲是無向圖,矩陣對稱
	}
}

/**
 * 鄰接矩陣的廣度優先遍歷操作
 * @param graph:圖結構
 */
void BFSTraverse(Graph graph)
{
    int i, j;
    QueueElemType q;
    Boolean bole;
    LinkQueue queue;

    for(i = 0; i < graph.numVertexs; i++)
    {
        visited[i] = FALSE;   // 初始化遍歷標誌數組
    }
    bole = initLinkQueue(&queue);   // 初始化隊列
    if(!bole) {
        exit(1);   // 隊列初始化失敗
    }
    // 循環每一個頂點
    for(i = 0; i < graph.numVertexs; i++)
    {
        // 第i個頂點還未被訪問過
        if(!visited[i])
        {
            visited[i] = TRUE;   //標誌該頂點已經被訪問過了
            printf("%c ", graph.vexs[i]);
            bole = insertLinkQueue(&queue, i);   // 入隊列
            if(!bole)
            {
                exit(1);    // 入隊列失敗
            }
            // 隊列不爲空,成功出隊列
            while(deleteLinkQueue(&queue, &q))
            {
                for(j = 0; j < graph.numVertexs; j++)
                {
                    // 其他頂點與該頂點存在邊且還沒被訪問過
                    if(graph.arc[q][j] == 1 && !visited[j])
                    {
                        visited[j] = TRUE;        // 標記該頂點已經被訪問過了
                        printf("%c ", graph.vexs[j]);
                        bole = insertLinkQueue(&queue, j);
                        if(!bole)
                        {
                            exit(1);    // 入隊列失敗
                        }
                    }
                }
            }
        }
    }
}

int main()
{
    Graph graph;

    CreateGraph(&graph);
    BFSTraverse(graph);
    return 0;
}
五、對於無向圖採用鄰接表存儲方式的廣度優先遍歷完整代碼如下:
/***************************************************************************/
/**                 圖的遍歷之廣度優先遍歷(鄰接表存儲)                        **/
/***************************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define GRAPH_MAX_VERTEX_SIZE 100                    // 最大頂點數
#define TRUE 1
#define FALSE 0

typedef char VertexType;                             // 頂點類型
typedef int QueueElemType;   // 隊列元素數據類型
typedef int Boolean;

Boolean visited[GRAPH_MAX_VERTEX_SIZE];    // 遍歷標誌數組

// 邊表結點結構定義
typedef struct EdgeNode
{
	int adjvex;                                      // 鄰接點域,存儲該頂點在頂點表中對應的下標
	struct EdgeNode *next;                           // 鏈表指針域,指向下一個鄰接點
}EdgeNode;

// 頂點表結點結構定義
typedef struct VertexNode
{
	VertexType data;                                 // 頂點域,存儲頂點信息
	EdgeNode *firstEdge;                             // 邊表頭指針
}VertexNode;

// 圖結構定義
typedef struct Graph
{
	VertexNode adjList[GRAPH_MAX_VERTEX_SIZE];
	int numVertexs, numEdges;                        // 圖中當前頂點數和邊數
}Graph;

// 定義隊列元素結構
typedef struct QueueNode
{
    QueueElemType data;
    struct QueueNode *next;
}QueueNode;

// 定義隊列結構
typedef struct
{
    QueueNode *front;  // 對頭指針
    QueueNode *rear;   // 對尾指針
}LinkQueue;

/**
 * 建立無向圖的鄰接表表示
 * @param graph:指向圖結構的指針
 */
void CreateGraph(Graph *graph)
{
    int i, j, k;
    EdgeNode *e;

    printf("輸入無向圖的頂點數和邊數,分別用空格分隔:");
    scanf("%d %d", &(graph->numVertexs), &(graph->numEdges));  // 接收輸入的頂點數和邊數,並賦值

    // 讀入頂點信息,建立頂點表
    for(i = 0; i < graph->numVertexs; i++)
    {
        printf("輸入第%d個頂點信息:", i + 1);
        fflush(stdin);             // 清空鍵盤輸入緩衝區
        scanf("%c", &(graph->adjList[i].data));
        graph->adjList[i].firstEdge = NULL;     // 將該頂點指向第一個邊表結點的指針置爲空
    }

    // 建立邊表
    for(k = 0; k < graph->numEdges; k++)
    {
        printf("輸入邊(vi, vj)的兩個頂點在頂點數組中的下標i、下標j,分別用空格分隔:");
        fflush(stdin);           // 清空鍵盤輸入緩衝區
        scanf("%d %d", &i, &j);
        e = (EdgeNode *)malloc(sizeof(EdgeNode));  // 生成邊表結點
        if(!e)
        {
            exit(1);
        }
        e->adjvex = j;     // 鄰接序號爲j
        e->next = graph->adjList[i].firstEdge;   // 將邊表結點e的next指針指向當前頂點上firstEdge指針指向的邊表結點
        graph->adjList[i].firstEdge = e;         // 將當前頂點的firstEdge指針指向邊表結點e,頭插法

        // 因爲是無向圖,所以需要將(vi, vj)表示的邊反過來再執行一遍,設置下標爲j的頂點的邊表
        e = (EdgeNode *)malloc(sizeof(EdgeNode));
        if(!e)
        {
            exit(1);
        }
        e->adjvex = i;
        e->next = graph->adjList[j].firstEdge;
        graph->adjList[j].firstEdge = e;
    }
}

/**
 * 初始化一個空隊列,初始化成功返回TRUE,否則返回FALSE
 * @param queue:指向隊列結構的指針
 */
Boolean initLinkQueue(LinkQueue *queue)
{
    QueueNode *head;

    // 生成頭結點
    head = (QueueNode *)malloc(sizeof(QueueNode));

    if(!head)
    {
        return FALSE;
    }

    head->next = NULL;
    queue->front = queue->rear = head;
    return TRUE;
}

/**
 * 入隊列,入隊列成功返回TRUE,否則返回FALSE
 * @param queue:指向隊列結構的指針
 * @param data:入隊列元素
 */
Boolean insertLinkQueue(LinkQueue *queue, QueueElemType data)
{
    QueueNode *p;

    p = (QueueNode *)malloc(sizeof(QueueNode));
    if(!p)
    {
        return FALSE;
    }
    p->data = data;
    p->next = NULL;

    queue->rear->next = p;
    queue->rear = p;

    return TRUE;
}

/**
 * 出隊列,空隊列的情況下出隊列返回FALSE,否則返回TRUE
 * @param queue:指向隊列結構的指針
 * @param data:保存成功出隊列的元素
 */
Boolean deleteLinkQueue(LinkQueue *queue, QueueElemType *data)
{
    QueueNode *p;

    if(queue->front == queue->rear)
    {
        return FALSE;
    }

    p = queue->front->next;
    *data = p->data;

    queue->front->next = p->next;
    if(p == queue->rear)
    {
        queue->rear = queue->front;
    }
    free(p);
    return TRUE;
}

/**
 * 鄰接表的廣度優先遍歷操作
 * @param graph:圖結構
 */
void BFSTraverse(Graph graph)
{
    int i, j;
    QueueElemType q;
    Boolean bole;
    EdgeNode *e;
    LinkQueue queue;

    for(i = 0; i < graph.numVertexs; i++)
    {
        // 初始化遍歷標誌數組
        visited[i] = FALSE;
    }
    bole = initLinkQueue(&queue);   // 初始化隊列
    if(!bole)
    {
        exit(1); // 初始化隊列失敗
    }
    for(i = 0; i < graph.numVertexs; i++)
    {
        if(!visited[i])
        {
            visited[i] = TRUE;
            printf("%c ", graph.adjList[i].data);
            bole = insertLinkQueue(&queue, i);    // 入隊列
            if(!bole)
            {
                exit(1); // 入隊列失敗
            }
            // 隊列不爲空,成功出隊列
            while(deleteLinkQueue(&queue, &q))
            {
                e = graph.adjList[q].firstEdge;  // 當前結點指向邊表結果的頭指針
                while(e)
                {
                    j = e->adjvex;
                    // 未被訪問過
                    if(!visited[j])
                    {
                        visited[j] = TRUE;
                        printf("%c ", graph.adjList[j].data);
                        bole = insertLinkQueue(&queue, j);    // 入隊列
                        if(!bole)
                        {
                            exit(1); // 入隊列失敗
                        }
                    }
                    // 指向下一個鄰接點
                    e = e->next;
                }
            }
        }
    }
}

int main()
{
    Graph graph;

    CreateGraph(&graph);
    BFSTraverse(graph);
    return 0;
}


發佈了60 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章