数据结构——实验五·图

嗨~~欢迎来到Tubishu的博客🌸如果你也是一名在校大学生,正在寻找各种编程资源,那么你就来对地方啦🌟

Tubishu是一名计算机本科生,会不定期整理和分享学习中的优质资源,希望能为你的编程之路添砖加瓦⭐🔥

当然,如果你也好的资源推荐,欢迎在评论区分享,让我们共同打造一个丰富的编程资源库🔥🔥🔥

本文专栏 ➡️ 数据结构

图的遍历

本实验是基于C实现图的遍历操作,包括用邻接矩阵和邻接表创建有向图/无向图,深度优先和广度优先分别遍历。

实验目的:

  • 掌握图的逻辑结构
  • 掌握图的邻接矩阵、邻接表存储结构
  • 掌握图在邻接矩阵、邻接表上遍历算法的实现

实验内容:

(1)建立图的邻接矩阵、邻接表存储;

(2)对建立的图进行深度优先遍历;

(3)对建立的图进行广度优先遍历。

要求:设计菜单,根据菜单提示进行操作。

注:图遍历也涉及到堆栈与队列。在广度遍历中用到了队列,深度遍历中用到了堆栈。


实验产出:

1.核心代码:

cpp 复制代码
#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;                           // 生成新边表结点s
	    s->adjvex = j;                             // 邻接点序号为j
	    s->nextarc = G.vertices[i].firstarc;       // 将新边表结点s插入到顶点Vi的边表头部
	    G.vertices[i].firstarc = s;
		// 无向图 
	    if (flag == 1) {
	        s = new ArcNode;                       // 生成新边表结点s
	        s->adjvex = i;                         // 邻接点序号为i
	        s->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;                                     // 设置访问标志数组相应分量值为1
    printf("访问顶点: %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.运行结果:

这里以无向图的邻接矩阵为例


cpp 复制代码
	// 输出邻接矩阵 
	for (i = 0; i < G.vexnum; i++) {
		for (j = 0; j < G.vexnum; j++) 
            printf("%d ", G.arcs[i][j]);
            printf("\n"); 
	}

输出:

3.调试:

图空测试:

未创建图并尝试从图中弹出元素,验证程序是否能够正确检测到图空并输出相应的提示信息。

预期结果:程序应提示"请先输入图的顶点信息!"并阻止弹出操作。

输入信息错误调试:

输入不存在的顶点的边信息,验证程序是否能够正确检测到顶点信息错误并输出相应的提示信息。

预期结果:程序应提示"输入出错",并重新输入边的信息。


总结

不同表示法表示同一个图,其深度优先遍历与广度优先遍历的结果分别相同,即同一个图的邻接表和邻接矩阵的深度优先遍历结果相同,广度优先遍历的结果也相同。

如果你觉得这篇文章对你有所启发,请为博客点赞👍、收藏⭐️、评论💬或分享🔗,你的支持是Tubishu不断前行的源泉✨!衷心感谢你的鼓励与陪伴🙏!

若你有任何疑问、见解或补充,欢迎随时留言💬,让我们在交流中共同成长📚!❤️

愿各位大佬们在技术的道路上,代码顺畅无阻💻,思路清晰如光💡,不断突破自我,向着更高的目标迈进,实现自己的梦想!🎉

再次感谢你的阅读🌸

相关推荐
<但凡.6 分钟前
题海拾贝:力扣 138.随机链表的复制
数据结构·算法·leetcode
苦 涩1 小时前
考研408笔记之数据结构(六)——查找
数据结构
Bran_Liu2 小时前
【LeetCode 刷题】栈与队列-队列的应用
数据结构·python·算法·leetcode
苦 涩3 小时前
考研408笔记之数据结构(五)——图
数据结构·笔记·考研
小禾苗_3 小时前
数据结构——算法基础
数据结构
无限码力3 小时前
路灯照明问题
数据结构·算法·华为od·职场和发展·华为ode卷
嘻嘻哈哈樱桃3 小时前
前k个高频元素力扣--347
数据结构·算法·leetcode
dorabighead3 小时前
小哆啦解题记:加油站的奇幻冒险
数据结构·算法
卷卷的小趴菜学编程4 小时前
c++之List容器的模拟实现
服务器·c语言·开发语言·数据结构·c++·算法·list