图的遍历(BFS和DFS)

一、遍历

<1>遍历: 把所有元素都看一遍,每看到一个元素,针对条件进行处理
<2> 线性逻辑
        顺序存储
            void fun1(type *data, int num) {
                for (int i = 0; i < num; i++) {
                    // 逐个处理
                }
            }
            type : 如果是char类型, 字符串  
            {
                int i = 0;
                while (data[i]) {    //如果存在,继续遍历下一个
                    i++;
                }
            }

        链式存储  只有一个方向
            void fun2(Node *data) {
                Node *p = data;
                while (p) {
                    printf();
                    p = p->next;
                }
            }

<3> 非线性逻辑
        树逻辑 1:n 
            顺序存储
                void fun3(tree *data, int num) {
                    for (int i = 0; i < num; i++) {
                        if (data[i] != 不存在的值) {    // 判断节点是否存在,存在就访问
                            // 逐个处理
                        }
                    }
                }
            链式存储
                1:2  二叉树 p->left  p->right
                p = p->left ,此时从A节点走到了A节点左边,再想找回A,就没有办法
                层次遍历(广度遍历)
                    队列,访问了一个节点,就把他所有的一级任务,放入队列,不断出队,得到新的节点,
                        又把这个节点的任务,再放入队列
                深度遍历(递归,用栈来暂存了上一个状态)
                    递进去,归回来
        图逻辑  n:m
            每个节点进行访问时,1:m的遍历方式
            广度遍历
                邻接矩阵
                    访问了一个节点后,将m个任务(未激活)放入队列,等待后面再逐个执行(顺序)
                邻接表
            深度遍历 
                邻接矩阵
                    注意引起死循环的访问 栈溢出,怎么办?
                    再引入一个访问标记,一开始初始化为都没有访问到,一旦访问,激活这个节点
                        通过边的表示方式,再找下一个节点(没有访问)
                邻接表

二、深度优先搜索

深度优先搜索的过程类似于树的先序遍历,⾸先从例⼦中体会深度优先搜索。例如上图是⼀个⽆向
图,采⽤深度优先算法遍历这个图的过程为:
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. 最后⼀步需要判断是否所有顶点都被访问,如果还有没被访问的,以未被访问的顶点为第⼀个顶
点,继续依照上边的⽅式进⾏遍历。
根据上边的过程,可以得到上图通过深度优先搜索获得的顶点的遍历次序为:
V1 -> V2 -> V4 -> V8 -> V5 -> V3 -> V6 -> V7
所谓深度优先搜索,是从图中的⼀个顶点出发,每次遍历当前访问顶点的临界点,⼀直到访问的顶
点没有未被访问过的临界点为⽌。
1 然后采⽤依次回退的⽅式,查看来的路上每⼀个顶点是否有其它未被访问的临界点。访问完成后,
判断图中的顶点是否已经全部遍历完成,如果没有,以未访问的顶点为起始点,重复上述过程。
深度优先搜索是⼀个不断回溯的过程。

三、广度优先搜索

广度优先搜索类似于树的层次遍历。从图中的某⼀顶点出发,遍历每⼀个顶点时,依次遍历其所有
的邻接点,然后再从这些邻接点出发,同样依次访问它们的邻接点。按照此过程,直到图中所有被访问
过的顶点的邻接点都被访问到。
最后还需要做的操作就是查看图中是否存在尚未被访问的顶点,若有,则以该顶点为起始点,重复
上述遍历的过程。
还拿上图 的无向图为例,假设 V1 作为起始点,遍历其所有的邻接点 V2 和 V3 ,以 V2 为起始点,
访问邻接点 V4 和 V5 ,以 V3 为起始点,访问邻接点 V6 、 V7 ,以 V4 为起始点访问 V8 ,以 V5 为
起始点,由于 V5 所有的起始点已经全部被访问,所有直接略过, V6 和 V7 也是如此。
以 V1 为起始点的遍历过程结束后,判断图中是否还有未被访问的点,由于图 1 中没有了,所以整个
图遍历结束。遍历顶点的顺序为:
V1 -> V2 -> v3 -> V4 -> V5 -> V6 -> V7 -> V8

四、邻接矩阵两种遍历方法的代码实现

matrixGraph.h

#ifndef MATRIX_GRAPH_H
#define MATRIX_GRAPH_H
/* 图的邻接矩阵存储方式
 * - 描述顶点集合、边的集合
 * - 顶点用一维数组描述,边用二维数组表示
 * */
#include "../base.h"
// 邻接矩阵图的顶点结构
typedef struct {
	int no;			// 顶点的编号
	char *show;		// 图中顶点的显示数据,指针指向了一个常量空间,考试时可以不用写
}MatrixVertex;
// 邻接矩阵边的类型,用int来描述,即可以描述权值和是否有边
typedef int MatrixEdge;
// 邻接矩阵表示的图
typedef struct {
	MatrixVertex vex[MaxNodeNum];			// 存储顶点的信息
	int	nodeNum;							// 约束实际顶点的数量,邻接矩阵遍历时的最大值
	MatrixEdge edges[MaxNodeNum][MaxNodeNum];		// 邻接矩阵定义边的情况
	int edgeNum;							// 定义边的个数
	int directed;							// 是否是有向图
}MGraph;
/* 邻接矩阵图的初始化,初始化顶点集
 * num		:  顶点的个数
 * names	:  顶点显示的字符串,以字符指针来保存,上层空间他的值有效
 * directed :  是否是有向图
 * edgeValue:  初始化边的权值
 * */
void initMGraph(MGraph *g, int num, char *names[], int directed, int edgeValue);
/* 添加边的信息
 * x		:	起始顶点编号
 * y		:	终止顶点编号
 * w		:	该边的权值
 * */
void addMGraphEdge(MGraph *g, int x, int y, int w);
// 访问节点
void visitMGraphNode(MatrixVertex *node);
// 清空已访问记录表
void clearMGraphVisit();
// 深度遍历,从v号顶点开始遍历
void DFSMGraphTravel(MGraph *graph, int v);
// 广度遍历
void BFSMGraphTravel(MGraph *graph, int v);

#endif

matrixGraph.c

#include <string.h>
#include <stdio.h>
#include "matrixGraph.h"

int isEdge(int weight) {
	if (weight > 0 && weight < INF)
		return 1;
	return 0;
}

void initMGraph(MGraph *g, int num, char **names, int directed, int edgeValue) {
	g->directed = directed;
	g->edgeNum = 0;
	g->nodeNum = num;
	memset(g->vex, 0, sizeof(g->vex));
	memset(g->edges, 0, sizeof(MatrixEdge) * MaxNodeNum * MaxNodeNum);
	// 初始化顶点
	for (int i = 0; i < num; ++i) {
		g->vex[i].no = i;
		g->vex[i].show = names[i];
		for (int j = 0; j < num; ++j) {
			g->edges[i][j] = edgeValue;
		}
	}
}
// 简单图 不能有自环 不能有重边
void addMGraphEdge(MGraph *g, int x, int y, int w) {
	if (x < 0 || x > g->nodeNum)
		return;
	if (y < 0 || y > g->nodeNum)
		return;
	if (!isEdge(g->edges[x][y])) {
		g->edges[x][y] = w;
		if (g->directed == 0) {
			g->edges[y][x] = w;
		}
		g->edgeNum++;
	}
}

//访问节点
void visitMGraphNode(MatrixVertex *node) {
	printf("\t%s,", node->show);
}

// 记录是否访问的标记(到底有没有访问到,0未被访问,1被访问)
static int MGraphVisited[MaxNodeNum]; //默认数组全0

//深度遍历
void DFSMGraphTravel(MGraph *graph, int v) {
	visitMGraphNode(&graph->vex[v]);		// 访问v号节点
	MGraphVisited[v] = 1;
	// 从v号节点开始,通过邻接矩阵描述的边,找到一个未被访问的节点再进行DFS
	for (int i = 0; i < graph->nodeNum; ++i) {
        //判断边是否存在,且对应顶点未被访问
		if (isEdge(graph->edges[v][i]) && MGraphVisited[i] == 0) {
			DFSMGraphTravel(graph, i);
		}
	}
}

//清除已经访问的
void clearMGraphVisit() {
	memset(MGraphVisited, 0, sizeof(MGraphVisited)); //全清零
}

/* 图的广度遍历
 * 初始化 :一个队列(循环队列),把用户要访问的第一个节点放入队列
 * 循环执行任务:
 * 		队列不空,说明还有节点没有被访问,直到队列为空
 * 		循环判断队列为空
 * 		出队一个元素A,设置该元素A已经访问,将A的m个节点(未被访问)放入队列,设置为已访问状态
 * 		避免任务队列里已经有待处理的任务,而其他节点激活后,又放入到任务队列
 * 		继续循环
 * */
void BFSMGraphTravel(MGraph *graph, int v) {
	int que[MaxNodeNum];
	int rear = 0, front = 0; //队尾队头都指向0
	int cur;

	rear = (rear + 1) % MaxNodeNum;
	que[rear] = v;				// v放入任务队列
	while (front != rear) {
		// 出队
		front = (front + 1) % MaxNodeNum;
		cur = que[front];
		// 设置该节点以访问
		visitMGraphNode(&graph->vex[cur]); //访问节点
		MGraphVisited[cur] = 1; //标记为已被访问
		for (int i = 0; i < graph->nodeNum; ++i) {
            //先判断当前的边存在并且对应的顶点未被访问
			if (isEdge(graph->edges[cur][i]) && MGraphVisited[i] == 0) {
				rear = (rear + 1) % MaxNodeNum; 
				que[rear] = i;               // 入队
				MGraphVisited[i] = 1;		// 标记为已经访问,防止重复任务
			}
		}
	}
}

main.c

#include <stdio.h>
#include "matrixGraph.h"

static void setupMatrixGraph(MGraph *g1) {
	char *nodeNames[] = {"V1", "V2", "V3", "V4",
						 "V5", "V6", "V7", "V8"};
	initMGraph(g1, 8, nodeNames, 0, 0);
	addMGraphEdge(g1, 0, 1, 1);
	addMGraphEdge(g1, 0, 2, 1);
	addMGraphEdge(g1, 1, 3, 1);
	addMGraphEdge(g1, 1, 4, 1);
	addMGraphEdge(g1, 2, 5, 1);
	addMGraphEdge(g1, 2, 6, 1);
	addMGraphEdge(g1, 3, 7, 1);
	addMGraphEdge(g1, 4, 7, 1);
	addMGraphEdge(g1, 5, 6, 1);
}

int main() {
	MGraph g1;
	setupMatrixGraph(&g1);
	printf("have %d num!\n", g1.edgeNum);
	printf("深度遍历: ");
	clearMGraphVisit();
	DFSMGraphTravel(&g1, 0);
	printf("\n广度遍历: ");
	clearMGraphVisit();
	BFSMGraphTravel(&g1, 0);
	printf("\n");
	return 0;
}

运行结果:

五、邻接表中两种遍历方法的代码实现

adjacentList.h

#ifndef ADJACENT_LIST_H
#define ADJACENT_LIST_H
/* 图的邻接表,在节点集合中,增加指向边的指针
 * 边节点里包含了下一个和首节点连接的边
 * */
#include "../base.h"
// 边的结构
typedef struct arcEdge{
	int no;					// (从首节点)其他节点的编号
	int weight;				// 边的权重
	struct arcEdge *next;	// (从首节点)指向下一条边
}ArcEdge;
// 顶点结构
typedef struct {
	int no;					// 顶点的编号
	char *show;				// 顶点显示内容
	ArcEdge *firstEdge;		// 当前的顶点指向的边
}ArcNode;
// 使用邻接表描述的图
typedef struct {
	ArcNode *nodes;			// 图中顶点的集合
	int *visited;			// 图中顶点访问的标记
	int nodeNum;			// 图中顶点的个数
	int edgeNum;			// 图中边的个数
	int directed;			// 是否有向
}AGraph;
// 产生n个节点的邻接表的图
AGraph *createAGraph(int n);
void releaseAGraph(AGraph *graph);
/* 初始化邻接表的图
 * */
void initAGraph(AGraph *graph, int num, char *names[], int directed);
void addAGraphEdge(AGraph *graph, int x, int y, int w);
// 复位访问信息
void resetAGraphVisited(AGraph *graph);
// 访问节点
void visitAGraphNode(ArcNode *node);
// 深度搜索
void DFSAGraphTravel(AGraph *graph, int v);
// 广度搜索
void BFSAGraphTravel(AGraph *graph, int v);
#endif

adjacentList.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "adjacentList.h"

AGraph *createAGraph(int n) {
	AGraph *graph = (AGraph *) malloc(sizeof(AGraph));
	if (graph == NULL) {
		fprintf(stderr, "malloc failed!\n");
		return NULL;
	}
	graph->edgeNum = 0;
	graph->nodeNum = n;
	graph->nodes = (ArcNode *) malloc(sizeof(ArcNode) * n);
	graph->visited = (int *) malloc(sizeof(int ) * n);
	if (graph->nodes == NULL || graph->visited == NULL) {
		fprintf(stderr, "malloc node failed!\n");
		free(graph);
		return NULL;
	}
	// 初始化链表
	memset(graph->nodes, 0, sizeof(ArcNode) * n);
	return graph;
}

void releaseAGraph(AGraph *graph) {
	ArcEdge *tmp;
	int count = 0;
	if (graph) {
		for (int i = 0; i < graph->nodeNum; ++i) {	// 遍历每一个节点
			ArcEdge *edge = graph->nodes[i].firstEdge;
			while (edge) {
				tmp = edge;
				edge = edge->next;
				free(tmp);          //释放边
				count++;
			}
		}
		free(graph->nodes);      //释放节点
		free(graph->visited);   //释放访问信息
		free(graph);           //释放图
		printf("release %d edges!\n", count);
	}
}

void initAGraph(AGraph *graph, int num, char **names, int directed) {
	graph->directed = directed;
	for (int i = 0; i < num; ++i) {			// 为数组空间的num个顶点进行初始化
		graph->nodes[i].no = i;
		graph->nodes[i].show = names[i];
		graph->nodes[i].firstEdge = NULL;
	}
}

static ArcEdge *createArcEdge(int y, int w) {
	ArcEdge *edge = (ArcEdge *) malloc(sizeof(ArcEdge));
	edge->no = y;
	edge->weight = w;
	edge->next = NULL;
	return edge;
}

void addAGraphEdge(AGraph *graph, int x, int y, int w) {
	if (x < 0 || x >= graph->nodeNum || y < 0 || y >= graph->nodeNum)
		return;
	// 边节点采用头插法
	if (x == y)
		return;

	ArcEdge *edge = createArcEdge(y, w);
	edge->next = graph->nodes[x].firstEdge;
	graph->nodes[x].firstEdge = edge;
	graph->edgeNum++;
	if (graph->directed == 0) {		// 不是自环边,并且是无向图
		edge = createArcEdge(x, w);
		edge->next = graph->nodes[y].firstEdge;
		graph->nodes[y].firstEdge = edge;
		graph->edgeNum++;
	}
}

void visitAGraphNode(ArcNode *node) {
	printf("\t%s", node->show);
}

//深度遍历
void DFSAGraphTravel(AGraph *graph, int v) {
	ArcEdge *p;
	// 先访问v节点,遍历v节点中的其他边的情况,找到下一个节点,再递进去
	graph->visited[v] = 1;
	visitAGraphNode(&graph->nodes[v]);
	p = graph->nodes[v].firstEdge;
	while (p) {
		if (graph->visited[p->no] == 0) {
			DFSAGraphTravel(graph, p->no);
		}
		p = p->next;
	}
}

//复位(重置/清除)访问信息
void resetAGraphVisited(AGraph *graph) {
	if (graph && graph->visited) {
		memset(graph->visited, 0, sizeof(int ) * graph->nodeNum);
	}
}

//广度遍历
void BFSAGraphTravel(AGraph *graph, int v) {
	int *que = (int *) malloc(sizeof(int ) * graph->nodeNum); //队列
	int front = 0, rear = 0;
	int cur;
	ArcEdge *p;

	rear = (rear + 1) % graph->nodeNum; 
	que[rear] = v;                      
	while (front != rear) {
		front = (front + 1) % graph->nodeNum;
		cur = que[front];
        //出队后访问
		visitAGraphNode(&graph->nodes[cur]);
		graph->visited[cur] = 1;

		p = graph->nodes[cur].firstEdge;
		while (p) {		// 把当前的任务都加入队列
			if (graph->visited[p->no] == 0) {
                没有被访问则加入队列
				rear = (rear + 1) % graph->nodeNum; 
				que[rear] = p->no;
				graph->visited[p->no] = 1;
			}
			p = p->next;
		}
	}
	free(que);
}

main.c

#include "adjacentList.h"
#include <stdio.h>

static void setupGraph(AGraph *graph) {
	char *nodeNames[] = {"A", "B", "C", "D", "E"};
	initAGraph(graph, sizeof(nodeNames) / sizeof(nodeNames[0]),nodeNames, 1);
	addAGraphEdge(graph, 0, 4, 1);
	addAGraphEdge(graph, 0, 3, 1);
	addAGraphEdge(graph, 0, 1, 1);
	addAGraphEdge(graph, 1, 4, 1);
	addAGraphEdge(graph, 1, 2, 1);
	addAGraphEdge(graph, 2, 0, 1);
	addAGraphEdge(graph, 3, 2, 1);
}

int main() {
	int n = 5;
	AGraph *graph = createAGraph(n);
	setupGraph(graph);
	printf("边数: %d\n", graph->edgeNum);
	printf("图的深度遍历: ");
	DFSAGraphTravel(graph, 0);
	printf("\n图的广度遍历: ");
	resetAGraphVisited(graph);
	BFSAGraphTravel(graph, 0);
	printf("\n");
	releaseAGraph(graph);
	return 0;
}

运行结果:

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值