一、简介
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
#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
#include
#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
#include
#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
#include
#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;
}