一、遍历
<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;
}
运行结果: