图的两种遍历方式--深度优先与广度优先

1.深度优先遍历

深度优先遍历(Depth First Search),也称为深度优先搜索,简称为DFS。

DFS算法思路:
(1)在访问图中某一起始顶点V后,由V出发,访问它的任一邻接点W1;
(2)再从W1出发,访问与W1邻接但还未被访问过的顶点W2;
(3)然后再从W2出发,进行类似的访问,…
(4)如此进行下去,直至到达所有的邻接顶点都被访问过的顶点U为止;
(5)接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问过的邻接顶点;
(6)如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;
(7)如果没有,就再退回一步进行搜索,重复上述过程,直到连通图中所有的顶点都被访问过为止。

上述算法思路表明,DFS就是一个递归的过程,类似于一棵树的前序遍历。

1.1邻接矩阵的DFS

#include <iostream>
using namespace std;

typedef char VertexType;	// 顶点类型,自定义
typedef int  WeightType;	// 边上权值类型,自定义

#define MAXVEX (100)

int visited[MAXVEX] = { 0 };	// 辅助数组,记录每个顶点是否被访问过。0:未访问,1:已访问

typedef struct {
	VertexType vexs[MAXVEX];			// 顶点数组
	WeightType arcs[MAXVEX][MAXVEX];	// 邻接矩阵
	int Nv, Ne;							// 顶点数、边数
} MGraph;

/**
* @brief: 	建立图的邻接矩阵结构
* @param G:指向图的指针
*/
void createMGraph(MGraph* G) {
	cout << "输入顶点数和边数:";
	cin >> G->Nv >> G -> Ne;

	// 初始化邻接矩阵
	for (int i = 0; i < G->Nv; i++)
	{
		for (int j = 0; j < G->Nv; j++)
		{
			G->arcs[i][j] = 0;	// 无权图的邻接矩阵全部初始化为0,表示没有边
		}
	}

	cout << "输入顶点信息:";
	for (int i = 0; i < G->Nv; i++)
	{
		cin >> G->vexs[i];
	}

	// 读入Ne条边信息,建立邻接矩阵
	int i, j;
	for (int k = 0; k < G->Ne; k++)
	{
		cout << "输入边(Vi, Vj)的下标i、j:";
		cin >> i >> j;
		G->arcs[i][j] = 1;
		G->arcs[j][i] = G->arcs[i][j];	// 无向图的邻接矩阵为对称阵
	}
}

/**
* @brief: 邻接矩阵的DFS递归算法
* @param G:指向图的指针
* @param i:遍历的顶点编号
*/
void DFS(MGraph* G, int i) {
	cout << G->vexs[i] << "\t";		// 打印顶点值
	visited[i] = 1;					// 更新visited数组
	for (int j = 0; j < G->Nv; j++)
	{
		if (G->arcs[i][j] == 1 && visited[j] == 0)	// 对未访问过的邻接点递归调用DFS
		{
			DFS(G, j);
		}
	}
}

/**
* @brief: 	DFS的主控函数
* @param G:指向图的指针
*/
void DFSTraverse(MGraph* G) {
	for (int i = 0; i < G->Nv; i++)	// 对图的每个顶点DFS,防止图不连通,有顶点没用被遍历
	{
		if (0 == visited[i])
		{
			DFS(G, i);
		}
	}
}

int main() {
	MGraph* G = new MGraph;
	createMGraph(G);

	DFSTraverse(G);
	
	delete G;
}

运行结果:
以《大话数据结构》图7-5-2的无向图为例。
图的两种遍历方式--深度优先与广度优先

1.2邻接表的DFS

#include <iostream>
using namespace std;

#define MAXVEX (100)		// 最大顶点数

int visited[MAXVEX] = { 0 };	// 辅助数组,标记顶点是否被访问

typedef char VertexType;	// 顶点类型,自定义
typedef int  WeightType;	// 边上权值类型,自定义

typedef struct EdgeNode {
	int		  adjvex;		// 邻接点域,存储该顶点对应的下标
	EdgeNode* next;			// 链域,指向下一个邻接点
	// WeightType	weight;		// 权值域,存储边上的权值		无权图不需要
} EdgeNode;

typedef struct VertexNode {
	VertexType data;		// 顶点信息
	EdgeNode* firstEdge;	// 指向第一个邻接点
} VertexNode, AdjList[MAXVEX];

typedef struct {
	AdjList adjList;	// 邻接表
	int Nv, Ne;			// 顶点数、边数
} ALGraph;

/**
* @brief: 创建图的邻接表结构
* @param G:指向图的指针
*/
void createALGraph(ALGraph* G) {
	cout << "输入顶点数和边数:";
	cin >> G->Nv >> G->Ne;

	cout << "输入顶点信息:";
	for (int i = 0; i < G->Nv; i++)
	{
		cin >> G->adjList[i].data;			// 顶点信息
		G->adjList[i].firstEdge = nullptr;	// 边表指针初始化为空
	}

	int i, j;
	EdgeNode* e;
	for (int k = 0; k < G->Ne; k++)
	{
		cout << "输入边(Vi, Vj)的下标i、j:";
		cin >> i >> j;
		e = new EdgeNode;
		e->adjvex = j;
		e->next = G->adjList[i].firstEdge;	// 1
		G->adjList[i].firstEdge = e;		// 2
		// 1、2两步:头插法

		// 无向图的对称操作
		e = new EdgeNode;
		e->adjvex = i;
		e->next = G->adjList[j].firstEdge;
		G->adjList[j].firstEdge = e;
	}
}

/**
* @brief: 销毁图的邻接表
* @param G:指向图的指针
*/
void destroyAdjList(ALGraph* G) {
	for (int i = 0; i < G->Nv; i++)
	{
		while (G->adjList[i].firstEdge)
		{
			EdgeNode* e = G->adjList[i].firstEdge;
			G->adjList[i].firstEdge = e->next;
			delete e;
		}
	}
}

/**
* @brief: 邻接表的DFS递归算法
* @param G: 指向图的指针
* @param i: 顶点下标
*/
void DFS(ALGraph* G, int i) {
	cout << G->adjList[i].data << "\t";
	visited[i] = 1;
	EdgeNode* p = G->adjList[i].firstEdge;
	while (p)
	{
		if (0 == visited[p->adjvex])
		{
			DFS(G, p->adjvex);
		}
		p = p->next;
	}
}

/**
* @brief: 邻接表的DFS递归算法
* @param G: 指向图的指针
*/
void DFSTraverse(ALGraph* G) {
	for (int i = 0; i < G->Nv; i++)
	{
		if (0 == visited[i])
		{
			DFS(G, i);
		}
	}
}

int main() {
	ALGraph* G = new ALGraph;
	createALGraph(G);
	
	DFSTraverse(G);
	
	destroyAdjList(G);

	delete G;
}

运行结果:
以《大话数据结构》图7-5-2的无向图为例。
图的两种遍历方式--深度优先与广度优先

2.广度优先遍历

广度优先遍历(Breadth First Search),也称为广度优先搜索,简称为BFS。

BFS算法思路:
从图的某一结点出发,首先依次访问该结点的所有邻接点Vi1, Vi2, …, Vin;
再按这些顶点被访问的先后次序依次访问与它们相邻接的所有未被访问的顶点;
重复此过程,直至所有顶点均被访问为止。

2.1 邻接矩阵的BFS

#include <iostream>
using namespace std;

/**************************************************************************************
*********************************** 图结构定义及操作 *********************************** 
***************************************************************************************/
typedef char VertexType;	// 顶点类型,自定义
typedef int  WeightType;	// 边上权值类型,自定义

#define MAXVEX (100)		// 最大顶点数

typedef struct {
	VertexType vexs[MAXVEX];			// 顶点数组
	WeightType arcs[MAXVEX][MAXVEX];	// 邻接矩阵
	int Nv, Ne;							// 顶点数、边数
} MGraph;

/**
* @brief:	建立图的邻接矩阵结构
* @param G:指向图的指针
*/
void createMGraph(MGraph* G) {
	cout << "输入顶点数和边数:";
	cin >> G->Nv >> G->Ne;

	// 初始化邻接矩阵
	for (int i = 0; i < G->Nv; i++)
	{
		for (int j = 0; j < G->Nv; j++)
		{
			G->arcs[i][j] = 0;	// 无权图的邻接矩阵全部初始化为0,表示没有边
		}
	}

	cout << "输入顶点信息:";
	for (int i = 0; i < G->Nv; i++)
	{
		cin >> G->vexs[i];
	}

	// 读入Ne条边信息,建立邻接矩阵
	int i, j;
	for (int k = 0; k < G->Ne; k++)
	{
		cout << "输入边(Vi, Vj)的下标i、j:";
		cin >> i >> j;
		G->arcs[i][j] = 1;
		G->arcs[j][i] = G->arcs[i][j];	// 无向图的邻接矩阵为对称阵
	}
}

/**************************************************************************************
*********************************** 队列结构定义及操作 **********************************
***************************************************************************************/
typedef int QElemType;			// 队列元素类型
typedef struct
{
	QElemType data[MAXVEX + 1];	// 存储顶点下标的数组
	int front;					// 头指针,指向队列的第一个元素
	int rear;					// 尾指针,指向队列中最后一个元素的下一个位置
} Queue;

/*
* @brief:	初始化一个空队列
* @param q: 指向队列的指针
* @return:	true
*/
bool initQueue(Queue* q) {
	for (int i = 0; i <= MAXVEX; i++)
	{
		q->data[i] = -1;	// 数组元素全部初始化为-1
	}
	q->front = 0;
	q->rear = 0;
	return true;
}

/**
* @brief:	判断队列是否为空
* @param q: 指向队列的指针
* @return:	true / false
*/
bool queueEmpty(Queue* q) {
	return q->front == q->rear;
}

/**
* @brief:	判断队列是否已满
* @param q: 指向队列的指针
* @return:	true / false
*/
bool queueFull(Queue* q) {
	return q->rear == MAXVEX;
}

/*
* @brief:		元素入队
* @param q:		指向队列的指针
* @param elem:	需要入队的元素
* @return:		true / false
*/
bool enQueue(Queue* q, QElemType elem) {
	if (queueFull(q))	// 队列已满
	{
		return false;
	}	
	else
	{
		q->data[q->rear++] = elem;
		return true;
	}
}

/*
* @brief:		元素出队
* @param q:		指向队列的指针
* @param elem:	接收出队的元素
* @return:		true / false
*/
bool deQueue(Queue* q, QElemType* elem) {
	if (queueEmpty(q))	// 队列为空
	{
		return false;
	}
	else
	{
		*elem = q->data[q->front++];
		return true;
	}
}

/**************************************************************************************
************************************** BFS遍历算法 *************************************
***************************************************************************************/
int visited[MAXVEX] = { 0 };	// 辅助数组,记录每个顶点是否被访问过。0:未访问,1:已访问
/**
* @brief: 邻接矩阵的BFS递归算法
* @param G:指向图的指针
* @param i:遍历的顶点编号
*/
void BFSTraverse(MGraph* G) {
	Queue q;
	initQueue(&q);	// 初始化队列

	for (int i = 0; i < G->Nv; i++)		// 对每个点做循环,防止图不连通,有顶点没用被遍历
	{
		if (0 == visited[i])	// 该顶点尚未被访问
		{
			cout << G->vexs[i] << "\t";
			visited[i] = 1;		// 打印后将访问标记位置1
			enQueue(&q, i);		// 将图的第一个顶点入队
			while (!queueEmpty(&q))
			{
				QElemType e;	// 接收出队元素
				deQueue(&q, &e);

				// 将该顶点的所有邻接点入队
				for (int j = 0; j < G->Nv; j++)
				{
					if (G->arcs[e][j] == 1 && visited[j] == 0)
					{
						cout << G->vexs[j] << "\t";
						visited[j] = 1;
						enQueue(&q, j);
					}
				}
			}
		}
	}
}

int main() {
	MGraph* G = new MGraph;
	createMGraph(G);

	BFSTraverse(G);

	delete G;
}

运行结果:
以《大话数据结构》图7-5-3的无向图为例。
图的两种遍历方式--深度优先与广度优先

2.2 邻接表的BFS

#include <iostream>
using namespace std;

/**************************************************************************************
*********************************** 图结构定义及操作 ***********************************
***************************************************************************************/
typedef char VertexType;	// 顶点类型,自定义
typedef int  WeightType;	// 边上权值类型,自定义

#define MAXVEX (100)		// 最大顶点数

typedef struct EdgeNode {
	int		  adjvex;		// 邻接点域,存储该顶点对应的下标
	EdgeNode* next;			// 链域,指向下一个邻接点
	// WeightType	weight;		// 权值域,存储边上的权值		无权图不需要
} EdgeNode;

typedef struct VertexNode {
	VertexType data;		// 顶点信息
	EdgeNode* firstEdge;	// 指向第一个邻接点
} VertexNode, AdjList[MAXVEX];

typedef struct {
	AdjList adjList;	// 邻接表
	int Nv, Ne;			// 顶点数、边数
} ALGraph;

/**
* @brief:	创建图的邻接表结构
* @param G:	指向图的指针
* @return:	void
*/
void createALGraph(ALGraph* G) {
	cout << "输入顶点数和边数:";
	cin >> G->Nv >> G->Ne;

	cout << "输入顶点信息:";
	for (int i = 0; i < G->Nv; i++)
	{
		cin >> G->adjList[i].data;			// 顶点信息
		G->adjList[i].firstEdge = nullptr;	// 边表指针初始化为空
	}

	int i, j;
	EdgeNode* e;
	for (int k = 0; k < G->Ne; k++)
	{
		cout << "输入边(Vi, Vj)的下标i、j:";
		cin >> i >> j;
		e = new EdgeNode;
		e->adjvex = j;
		e->next = G->adjList[i].firstEdge;	// 1
		G->adjList[i].firstEdge = e;		// 2
		// 1、2两步:头插法

		// 无向图的对称操作
		e = new EdgeNode;
		e->adjvex = i;
		e->next = G->adjList[j].firstEdge;
		G->adjList[j].firstEdge = e;
	}
}

/**
* @brief: 销毁图的邻接表
* @param G:指向图的指针
* @return:	void
*/
void destroyAdjList(ALGraph* G) {
	for (int i = 0; i < G->Nv; i++)
	{
		while (G->adjList[i].firstEdge)
		{
			EdgeNode* e = G->adjList[i].firstEdge;
			G->adjList[i].firstEdge = e->next;
			delete e;
		}
	}
}

/**************************************************************************************
*********************************** 队列结构定义及操作 **********************************
***************************************************************************************/
typedef int QElemType;			// 队列元素类型
typedef struct
{
	QElemType data[MAXVEX + 1];	// 存储顶点下标的数组
	int front;					// 头指针,指向队列的第一个元素
	int rear;					// 尾指针,指向队列中最后一个元素的下一个位置
} Queue;

/*
* @brief:	初始化一个空队列
* @param q: 指向队列的指针
* @return:	true
*/
bool initQueue(Queue* q) {
	for (int i = 0; i <= MAXVEX; i++)
	{
		q->data[i] = -1;	// 数组元素全部初始化为-1
	}
	q->front = 0;
	q->rear = 0;
	return true;
}

/**
* @brief:	判断队列是否为空
* @param q: 指向队列的指针
* @return:	true / false
*/
bool queueEmpty(Queue* q) {
	return q->front == q->rear;
}

/**
* @brief:	判断队列是否已满
* @param q: 指向队列的指针
* @return:	true / false
*/
bool queueFull(Queue* q) {
	return q->rear == MAXVEX;
}

/*
* @brief:		元素入队
* @param q:		指向队列的指针
* @param elem:	需要入队的元素
* @return:		true / false
*/
bool enQueue(Queue* q, QElemType elem) {
	if (queueFull(q))	// 队列已满
	{
		return false;
	}
	else
	{
		q->data[q->rear++] = elem;
		return true;
	}
}

/*
* @brief:		元素出队
* @param q:		指向队列的指针
* @param elem:	接收出队的元素
* @return:		true / false
*/
bool deQueue(Queue* q, QElemType* elem) {
	if (queueEmpty(q))	// 队列为空
	{
		return false;
	}
	else
	{
		*elem = q->data[q->front++];
		return true;
	}
}

/**************************************************************************************
************************************** BFS遍历算法 *************************************
***************************************************************************************/
int visited[MAXVEX] = { 0 };	// 辅助数组,记录每个顶点是否被访问过。0:未访问,1:已访问
/**
* @brief: 邻接矩阵的BFS递归算法
* @param G:指向图的指针
* @param i:遍历的顶点编号
* @return:	void
*/
void BFSTraverse(ALGraph* G) {
	Queue q;
	initQueue(&q);	// 初始化队列

	for (int i = 0; i < G->Nv; i++)		// 对每个点做循环,防止图不连通,有顶点没用被遍历
	{
		if (0 == visited[i])	// 该顶点尚未被访问
		{
			cout << G->adjList[i].data << "\t";
			visited[i] = 1;		// 打印后将访问标记位置1
			enQueue(&q, i);		// 将图的第一个顶点入队
			while (!queueEmpty(&q))
			{
				QElemType e;	// 接收出队元素
				deQueue(&q, &e);
				EdgeNode* edge = G->adjList[e].firstEdge;
				// 将该顶点的所有邻接点入队
				while (edge)
				{
					if (visited[edge->adjvex] == 0)
					{
						cout << G->adjList[edge->adjvex].data << "\t";
						visited[edge->adjvex] = 1;
						enQueue(&q, edge->adjvex);
					}
					edge = edge->next;
				}
			}
		}
	}
}

int main() {
	ALGraph* G = new ALGraph;
	createALGraph(G);

	BFSTraverse(G);

	destroyAdjList(G);

	delete G;
}

运行结果:
以《大话数据结构》图7-5-3的无向图为例。
图的两种遍历方式--深度优先与广度优先

3.参考书籍

大话数据结构-程杰
青岛大学数据结构-王卓

上一篇:java 服务端生成图片验证码及验证


下一篇:IDEA的快捷注释