一、遍历
<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个任务(未激活)放入队列,等待后面再逐个执行(顺序)
邻接表
深度遍历
邻接矩阵
注意引起死循环的访问 栈溢出,怎么办?
再引入一个访问标记,一开始初始化为都没有访问到,一旦访问,激活这个节点
通过边的表示方式,再找下一个节点(没有访问)
邻接表
二、深度优先搜索
三、广度优先搜索
四、邻接矩阵两种遍历方法的代码实现
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;
}
运行结果: