嗨~~欢迎来到Tubishu的博客🌸如果你也是一名在校大学生,正在寻找各种编程资源,那么你就来对地方啦🌟
Tubishu是一名计算机本科生,会不定期整理和分享学习中的优质资源,希望能为你的编程之路添砖加瓦⭐🔥
当然,如果你也好的资源推荐,欢迎在评论区分享,让我们共同打造一个丰富的编程资源库🔥🔥🔥
本文专栏 ➡️ 数据结构
图的遍历
本实验是基于C实现图的遍历操作,包括用邻接矩阵和邻接表创建有向图/无向图,深度优先和广度优先分别遍历。
实验目的:
- 掌握图的逻辑结构
- 掌握图的邻接矩阵、邻接表存储结构
- 掌握图在邻接矩阵、邻接表上遍历算法的实现
实验内容:
(1)建立图的邻接矩阵、邻接表存储;
(2)对建立的图进行深度优先遍历;
(3)对建立的图进行广度优先遍历。
要求:设计菜单,根据菜单提示进行操作。
注:图遍历也涉及到堆栈与队列。在广度遍历中用到了队列,深度遍历中用到了堆栈。
实验产出:
1.核心代码:
#include<stdio.h>
#include<stdlib.h>#define ERROR 0
#define OK 1// 定义循环队列
#define MAXQSIZE 1024
typedef char QElemType;
typedef struct {QElemType data[MAXQSIZE]; // 数据的存储区 int front, rear; // 队头、队尾指针 int num; // 队中元素个数
} c_SeqQ;#define MaxVerNum 100 // 最大订点数为100
#define MAxInt 32767 // 表示极大值 即 ∞// 定义邻接矩阵数据类型
typedef char VertexType; // 顶点类型
typedef int ArcType; // 边的权值
typedef int OtheInfo;
typedef struct {VertexType vexs[MaxVerNum]; // 顶点表,储存顶点信息 ArcType arcs[MaxVerNum][MaxVerNum]; // 邻接矩阵,即边表 int vexnum, arcnum;
} AMGraph;// 定义邻接表数据类型
typedef struct ArcNode { // 边结点 int adjvex; // 该边所指向的顶点的位置 struct ArcNode *nextarc; // 指向下一条边的指针 OtheInfo info;
} ArcNode;typedef struct VNode { // 顶点表结点 VertexType data; // 顶点信息 ArcNode *firstarc; // 指向第一条依附该顶点的边指针
} VNode, AdjList[MaxVerNum];typedef struct {AdjList vertices; // 邻接表 int vexnum, arcnum;
} ALGraph;// 循环队列的初始化
int InitQueue_Sq(c_SeqQ *&Q) {if (!(Q = new c_SeqQ)) return ERROR;Q->front = Q->rear = 0;Q->num = 0;return OK;
}// 销毁顺序队列
int DestroyQueue_Sq(c_SeqQ *Q) {if (!Q) {free(Q); // delete Q;return OK;}return ERROR;
}// 判断顺序队列空
int QueueEmpty_Sq(c_SeqQ *Q) {return (Q->num == 0);
}// 入队操作
int EnQueue_Sq(c_SeqQ *Q, QElemType e) {if (Q->num == MAXQSIZE) return ERROR;Q->data[Q->rear] = e;Q->rear = (Q->rear + 1) % MAXQSIZE;Q->num++;return OK;
}// 出队操作
int DeQueue_Sq(c_SeqQ *Q, QElemType &e) {if (Q->num == 0) return ERROR;e = Q->data[Q->front];Q->front = (Q->front + 1) % MAXQSIZE;Q->num--;return OK;
}// 建立图G的邻接矩阵存储
void CreateAMGraph(AMGraph &G) {int i, j, k;int flag = 1;printf("图为有向图,还是无向图,输入1,表示无向图,否则,表示有向图: ");scanf(" %d", &flag);printf("请输入顶点数和边数(输入格式为:顶点数,边数): ");scanf(" %d,%d", &G.vexnum, &G.arcnum);printf("请输入顶点信息(例如:若有 5个顶点,则连续输入 ABCDE): ");for (i = 0; i < G.vexnum; i++) {scanf(" %c", &G.vexs[i]);}for (i = 0; i < G.vexnum; i++) for (j = 0; j < G.vexnum; j++) G.arcs[i][j] = 0; // 初始化邻接矩阵 printf("\n\t注意:顶点序列对应的序号从 0 起始编号,即 0, 1, 2, ... \n\n");printf("请输入每条边对应的两个顶点的序号(输入格式为:i,j <cr>)\n");for (k = 0; k < G.arcnum; k++) {printf("\n");for (i = 0; i < G.vexnum; i++)printf("%c--%d\n", G.vexs[i], i);printf("请输入第%d条边: ", k + 1);scanf("%d,%d", &i, &j); // 输入边,建立邻接矩阵if (i < 0 || i >= G.vexnum || j < 0 || j >= G.vexnum) {printf("输入出错!\n");k--;continue;}printf("(%c--%c)\n", G.vexs[i], G.vexs[j]);G.arcs[i][j] = 1;// 无向图的邻接矩阵存储建立if (flag == 1)G.arcs[j][i] = 1;}// 输出邻接矩阵 printf("\n邻接矩阵:\n");for (i = 0; i < G.vexnum; i++) {for (j = 0; j < G.vexnum; j++) printf("%d ", G.arcs[i][j]);printf("\n"); }
}// 建立图G的邻接表存储
void CreateALGraph(ALGraph &G) {int i, j, k;ArcNode *s;int flag = 1;printf("图为有向图,还是无向图,输入1,表示无向图,否则,表示有向图: ");scanf(" %d", &flag);printf("请输入顶点数和边数(输入格式为:顶点数,边数): ");scanf(" %d,%d", &G.vexnum, &G.arcnum); // 读入顶点和边数 printf("请输入顶点信息(例如:若有 5个顶点,则连续输入 ABCDE): ");for (i = 0; i < G.vexnum; i++) { // 建立有n个顶点的顶点表 scanf(" %c", &G.vertices[i].data); // 读入顶点信息 G.vertices[i].firstarc = NULL; // 顶点的边表头指针设为空 }printf("\n\t注意:顶点序列对应的序号从 0 起始编号,即 0, 1, 2, ... \n\n");printf("请输入每条边对应的两个顶点的序号(输入格式为: i,j <cr>)\n");for (k = 0; k < G.arcnum; k++) { // 建立边表printf("\n");for (i = 0; i < G.vexnum; i++)printf("%c--%d\n", G.vertices[i].data, i);printf("请输入第%d条边: ", k + 1);scanf("%d,%d", &i, &j); // 读入边(Vi,Vj)或<Vi,Vj>的顶点对应序号if (i < 0 || i >= G.vexnum || j < 0 || j >= G.vexnum) {printf("输入出错!\n");k--;continue;}printf("(%c--%c)\n", G.vertices[i].data, G.vertices[j].data);s = new ArcNode; // 生成新边表结点ss->adjvex = j; // 邻接点序号为js->nextarc = G.vertices[i].firstarc; // 将新边表结点s插入到顶点Vi的边表头部G.vertices[i].firstarc = s;// 无向图 if (flag == 1) {s = new ArcNode; // 生成新边表结点ss->adjvex = i; // 邻接点序号为is->nextarc = G.vertices[j].firstarc; // 将新边表结点s插入到顶点Vj的边表头部G.vertices[j].firstarc = s;}}
}// 深度优先搜索遍历一个连通图(图的存储结构采用邻接矩阵表示)
// 从第v个顶点出发递归地深度优先遍历图G,图的存储结构采用邻接矩阵表示。
// 第1个形参AMGraph G是指要遍历的图的存储结构
// 第2个形参int v是指从序号为v的顶点开始深度优先遍历图
// 第3个形参int visited[]数组,指示顶点是否被访问过
void DFSM(AMGraph G, int v, int visited[]) {visited[v] = 1; // 设置访问标志数组相应分量值为1printf("访问顶点: %d --- %c\n", v, G.vexs[v]); // 输出正访问的顶点for (int w = 0; w < G.vexnum; w++) // 依次检查邻接矩阵v所在的行if ((G.arcs[v][w] != 0) && (!visited[w])) // G.arcs[v][w]!=0表示w是v的邻接顶点DFSM(G, w, visited); // 如果w未访问,则递归调用DFSM}// 深度优先遍历图(图的存储结构采用邻接矩阵表示)
void DFSMTraverse(AMGraph &G) {int v, visited[MaxVerNum];for (v = 0; v < G.vexnum; v++) // 初始化visited数组,该数组每一个元素对应图的一个顶点 visited[v] = 0; // 用来标识顶点是否被访问过 for (v = 0; v < G.vexnum; v++) {if (!visited[v]) {printf("顶点 %c 为起点的深度优先遍历:\n", G.vexs[v]);DFSM(G, v, visited); // 对未访问过的顶点调用DFS }}
}// 深度优先遍历邻接表
void DFSL(ALGraph G, int v, int visited[]) {ArcNode *p;visited[v] = 1;printf("访问顶点: %d --- %c\n", v, G.vertices[v].data);p = G.vertices[v].firstarc;while (p) {if (!visited[p->adjvex]) {DFSL(G, p->adjvex, visited);}p = p->nextarc;}
}// 深度优先遍历邻接表的主函数
void DFSLTraverse(ALGraph &G) {int v, visited[MaxVerNum];for (v = 0; v < G.vexnum; v++) {visited[v] = 0;}for (v = 0; v < G.vexnum; v++) {if (!visited[v]) {printf("顶点 %c 为起点的深度优先遍历:\n", G.vertices[v].data);DFSL(G, v, visited);}}
}// 广度优先遍历邻接矩阵
void BFSM(AMGraph G, int v, int visited[]) {c_SeqQ *queue;InitQueue_Sq(queue);QElemType u;visited[v] = 1;printf("访问顶点: %d --- %c\n", v, G.vexs[v]);EnQueue_Sq(queue, v);while (!QueueEmpty_Sq(queue)) {DeQueue_Sq(queue, u); // 出队操作 for (int w = 0; w < G.vexnum; w++) {if (G.arcs[u][w] != 0 && !visited[w]) {visited[w] = 1;printf("访问顶点: %d --- %c\n", w, G.vexs[w]);EnQueue_Sq(queue, w);}}}DestroyQueue_Sq(queue);
}// 广度优先遍历邻接矩阵的主函数
void BFSMTraverse(AMGraph &G) {int v, visited[MaxVerNum];for (v = 0; v < G.vexnum; v++) {visited[v] = 0;}for (v = 0; v < G.vexnum; v++) {if (!visited[v]) {printf("顶点 %c 为起点的广度优先遍历:\n", G.vexs[v]);BFSM(G, v, visited);}}
}// 广度优先搜索遍历函数(图的存储结构采用邻接表表示)
// 第1个形参 ALGraph &G是指要遍历的图的存储结构
// 第2个形参int v是指从序号为v的顶点开始广度优先遍历图
// 第3个形参int visited数组,指示顶点是否被访问过
void BFSL(ALGraph &G, int v, int visited[]) {c_SeqQ *queue = NULL; // 定义一个顺序队列InitQueue_Sq(queue); // 初始化队列QElemType u;ArcNode *p; // 定义边指针visited[v] = 1; // 标识序号为 v 的顶点被访问过printf("访问顶点: %d --- %c\n", v, G.vertices[v].data); // 输出正访问的顶点if (EnQueue_Sq(queue, v) == 0) { // 把表头访问过的点放入队列中printf("队溢出,操作错误,结束!\n");exit(1); // 队列满,溢出}while (!QueueEmpty_Sq(queue)) { // 队列不空DeQueue_Sq(queue, u);p = G.vertices[u].firstarc;while (p) { // 遍历顶点v的所有邻接点if (visited[p->adjvex] == 0) { // 判断p->adjvex顶点是否被访问过visited[p->adjvex] = 1; // 若没被访问过,则对其进行访问printf("访问顶点: %d --- %c\n", p->adjvex, G.vertices[p->adjvex].data); // 输出正访问的结点if (EnQueue_Sq(queue, p->adjvex) == 0) { // 把访问过的点放入队列中printf("队溢出,操作错误,结束!\n");exit(1); // 队列满,溢出}}p = p->nextarc; // 指向下一个相邻顶点}}DestroyQueue_Sq(queue); // 删除队列
}// 广度优先遍历图(图的存储结构采用邻接表表示)
void BFSLTraverse(ALGraph &G) {int v, visited[MaxVerNum];// 初始化 visited 数组,该数组每一个元素对应图的一个顶点,用来标识顶点是否被访问过for (v = 0; v < G.vexnum; v++) {visited[v] = 0;}// 遍历所有顶点,对未访问的顶点执行 BFS 搜索for (v = 0; v < G.vexnum; v++) {if (!visited[v]) {printf("顶点 %c 为起点的广度优先遍历:\n", G.vertices[v].data);BFSL(G, v, visited); // v 未访问过,从 v 开始 BFS 搜索}}
}// 显示菜单
void showmenu() {printf("\n\n\n"); printf("\t* --图的遍历-- *\n");printf("\t**************************************************\n");printf("\t* 1---------用邻接矩阵表示一个图 *\n");printf("\t* 2---------用邻接表表示一个图 *\n");printf("\t* 3---------深度优先搜索遍历图(邻接矩阵) *\n");printf("\t* 4---------深度优先搜索遍历图(邻接表) *\n");printf("\t* 5---------广度优先搜索遍历图(邻接矩阵) *\n");printf("\t* 6---------广度优先搜索遍历图(邻接表) *\n");printf("\t* *\n");printf("\t* 0---------退出 *\n");printf("\t**************************************************\n");}// 图的操作
void graphOP() {char choice = 'N';int adjacency_matrix = 0; // 标志邻接矩阵是否存在int adjacency_list = 0; // 标志邻接表是否存在AMGraph G; // 邻接矩阵ALGraph T; // 邻接表while (choice != '0') {printf("\n请选择菜单号(0--6): ");scanf(" %c", &choice);switch (choice) {case '1':CreateAMGraph(G); // 创建图的邻接矩阵adjacency_matrix = 1;break;case '2':CreateALGraph(T); // 创建图的邻接表adjacency_list = 1;break;case '3': // 深度优先搜索遍历图(邻接矩阵)if (adjacency_matrix) {DFSMTraverse(G);} else {printf("请先输入图的顶点信息!\n");}break;case '4': // 深度优先搜索遍历图(邻接表)if (adjacency_list) {DFSLTraverse(T);} else {printf("请先输入图的顶点信息!\n");}break;case '5': // 广度优先搜索遍历图(邻接矩阵)if (adjacency_matrix) {BFSMTraverse(G);} else {printf("请先输入图的顶点信息!\n");}break;case '6': // 广度优先搜索遍历图(邻接表)if (adjacency_list) {BFSLTraverse(T);} else {printf("请先输入图的顶点信息!\n");}break;case '0':printf("\n\t 程序结束! \n");break;default:printf("\n\t 输入错误, 请重新输入! \n");}}
}int main() {
showmenu();
graphOP();
}
2.运行结果:
这里以无向图的邻接矩阵为例
// 输出邻接矩阵 for (i = 0; i < G.vexnum; i++) {for (j = 0; j < G.vexnum; j++) printf("%d ", G.arcs[i][j]);printf("\n"); }
输出:
3.调试:
图空测试:
未创建图并尝试从图中弹出元素,验证程序是否能够正确检测到图空并输出相应的提示信息。
预期结果:程序应提示“请先输入图的顶点信息!”并阻止弹出操作。
输入信息错误调试:
输入不存在的顶点的边信息,验证程序是否能够正确检测到顶点信息错误并输出相应的提示信息。
预期结果:程序应提示“输入出错”,并重新输入边的信息。
总结
不同表示法表示同一个图,其深度优先遍历与广度优先遍历的结果分别相同,即同一个图的邻接表和邻接矩阵的深度优先遍历结果相同,广度优先遍历的结果也相同。
如果你觉得这篇文章对你有所启发,请为博客点赞👍、收藏⭐️、评论💬或分享🔗,你的支持是Tubishu不断前行的源泉✨!衷心感谢你的鼓励与陪伴🙏!
若你有任何疑问、见解或补充,欢迎随时留言💬,让我们在交流中共同成长📚!❤️
愿各位大佬们在技术的道路上,代码顺畅无阻💻,思路清晰如光💡,不断突破自我,向着更高的目标迈进,实现自己的梦想!🎉
再次感谢你的阅读🌸