数据结构-图-领接表存储
一、了解图的领接表存储
1、定义与结构
-
定义:邻接表是图的一种链式存储结构,它通过链表将每个顶点与其相邻的顶点连接起来。
-
结构:
- 顶点表:通常使用一个数组来存储图的顶点信息,数组的每个元素对应一个顶点,元素的内容可以是顶点的标识符或值,以及一个指向该顶点邻接链表的指针。
- 邻接链表:对于图中的每个顶点,创建一个链表,链表中存储了与该顶点相邻的其他顶点。链表中的每个节点通常包含两个字段:邻接点域(指示与当前顶点相邻的顶点在图中的位置)和链域(指示下一条邻接边的节点)。
2、性质与特点
- 节省空间:对于稀疏图,邻接表能够节省大量的内存空间,因为它只存储实际存在的边,而不需要像邻接矩阵那样为所有可能的边分配空间。
- 灵活性强:邻接表易于增加和删除顶点及其邻接边,因为链表结构允许动态调整。
- 查找效率高:在查找与某个顶点相邻的所有顶点时,只需要遍历该顶点的邻接链表,而不需要遍历整个矩阵,从而提高了查找效率。
3、构建与存储
-
构建方法:
- 确定图的顶点数量和边的关系。
- 创建一个顶点表数组,数组的每个元素对应一个顶点,并初始化其邻接链表为空。
- 根据图的边关系,将每条边对应的两个顶点添加到彼此的邻接链表中。
-
存储:
- 顶点表数组通常存储在内存中,以便快速访问。
- 邻接链表使用动态内存分配来存储,以适应不同顶点的邻接边数量。
4、应用与限制
-
应用:
- 社交网络中的用户关系可以用无向图表示,每个用户是一个顶点,好友关系是一条边。使用邻接表存储后,可以方便地查找某个用户的好友列表或共同好友等信息。
- 在图的遍历算法(如广度优先搜索BFS和深度优先搜索DFS)中,邻接表提供了一种高效的访问相邻顶点的方式。
- 邻接表也适用于求解最短路径、网络流问题等图论算法。
-
限制:
- 在查找特定的边时,可能需要遍历整个邻接表,相对较慢,特别是对于稠密图。
- 对于有向图,如果需要同时获取顶点的入度和出度,则需要分别维护邻接表和逆邻接表,这会增加存储空间的开销。
5、示例
假设有一个有向图G,其顶点集合为V={V1, V2, V3, V4},边集合为E={(V1, V2), (V1, V4), (V2, V1), (V2, V3), (V3, V0), (V4, V3)}(注意:这里V0可能是一个错误或假设的顶点,通常顶点编号应从1开始且连续,但为保持示例的一致性,我们在此保留V0)。
该图的邻接表表示如下:
- V1的邻接链表包含:V2, V4
- V2的邻接链表包含:V1, V3
- V3的邻接链表包含:V0(注意:V0可能表示一个不存在的顶点或错误,根据实际情况调整)
- V4的邻接链表包含:V3
二、领接表结构(C语言)
1. 弧的结构体
#define MaxVertexNum 100
// 定义顶点对应的数据类型,这里使用字符类型来表示顶点
typedef char VertexType;// 定义边对应的数据类型,这里使用整数类型来表示边的权重(如果有的话)
typedef int EdgeType;// 定义邻接表中的弧(边)节点结构
typedef struct ArcNode {int adjvex; // adjvex 表示该弧所指向的顶点的位置(索引)struct ArcNode* nextarc; // nextarc 指向下一条与该顶点相连的弧的指针,形成链表// EdgeType data; // 边的数据(如权重),如果需要使用边的权重,取消注释
} ArcNode; // 弧节点结构定义结束
2. 顶点结构体
// 定义邻接表中的顶点节点结构
typedef struct VNode {VertexType name[20]; // name 存储顶点的名称或标识符,这里假设最长为19个字符加上一个结束符ArcNode* firstarc; // firstarc 指向该顶点的第一条相连的弧的指针,即该顶点的邻接链表的头指针
} VNode, AdjList[MaxVertexNum]; // VNode 和 AdjList 是同义类型,AdjList 是一个顶点数组类型,其大小由 MaxVertexNum 决定
3. 定义图,用领接表表示
// 定义图的结构,使用邻接表表示
typedef struct {int vexnum, arcnum; // vexnum 表示图的顶点数,arcnum 表示图的边数AdjList vertices; // vertices 是邻接表,存储了图的所有顶点及其邻接链表
} ALGraph;
4. 初始化图
// 初始化图的邻接表表示的函数
void InitALGraph(ALGraph* G) {// 初始化图的边数为0G->arcnum = 0;// 初始化图的顶点数为0G->vexnum = 0;// 遍历顶点数组,初始化每个顶点的邻接链表为空(即每个顶点的 firstarc 指针指向 NULL)for (int i = 0; i < MaxVertexNum; i++) {G->vertices[i].firstarc = NULL;}
}
三、领接表存储使用案例
实现的功能包括多个部分,涉及文件操作、从终端读取图的信息(由用户输入)、将图信息写入文件、从文件读取图信息、有向图转换为无向图以及查看图的信息。
1. 判断图中是否有a指向b
// 判断在有向图 G 中,顶点 a 是否有一条直接指向顶点 b 的边
bool VertexConnect(ALGraph G, int a, int b) {// 从顶点 a 的邻接链表的头指针开始遍历ArcNode* p = G.vertices[a].firstarc;// 遍历顶点 a 的所有邻接顶点for (; p != NULL; p = p->nextarc) {// 如果当前邻接顶点的位置(索引)等于 b,则返回 true,表示 a 指向 bif (p->adjvex == b) {return true;}}// 如果遍历完所有邻接顶点都没有找到指向 b 的边,则返回 falsereturn false;
}
2. 将图中a连向b
// 将图中顶点 a 与顶点 b 相连
void ConnectAB(ALGraph* G, int a, int b) {ArcNode* p, * t; // 声明两个指针,p 用于遍历,t 用于存储新创建的邻接点// 初始化指针 p,指向顶点 a 的首条邻接边p = G->vertices[a].firstarc;// 为新邻接点分配内存// 创建新的邻接点t = (ArcNode*)malloc(sizeof(ArcNode)); // 分配内存给新节点// 检查内存分配是否成功if (t == NULL) {printf("空间不足\n"); // 打印错误信息exit(1); // 内存分配失败,退出程序}// 设置新邻接点的信息t->adjvex = b; // 设置新节点的目标顶点索引为 bt->nextarc = NULL; // 新节点为链表末尾,nextarc 指向 NULL// 若顶点 a 无邻接边,则新边为首条边if (p == NULL) {G->vertices[a].firstarc = t; // 将新节点设置为顶点 a 的首条邻接边return; // 添加完成,直接返回}// 若顶点 a 有邻接边,则将新边添加到邻接链表末尾// 将 p 调整到最后一条边while (p->nextarc != NULL) { // 遍历至链表末尾p = p->nextarc; // 移动指针至下一条边}// 将新边添加到顶点 a 的邻接链表末尾p->nextarc = t; // 将链表的最后一个节点的 nextarc 指向新节点}
3 通过用户输入创建图
// 通过用户输入创建图
void StructGraph(ALGraph* G) {// 初始化数据G->arcnum = 0;G->vexnum = 0;// 假设 MaxVertexNum 已经在其他地方定义printf("请输入图的总顶点数(<=%d) : ", MaxVertexNum);scanf("%d", &G->vexnum);if (G->vexnum > MaxVertexNum || G->vexnum <= 0) {printf("图的顶点数输入错误!\n");exit(1);}// 初始化顶点数组for (int i = 0; i < G->vexnum; i++) {G->vertices[i].firstarc = NULL;printf("\n请输入第 %d 顶点的名字: ", i);scanf("%s", G->vertices[i].name); // 确保 name 数组足够大以存储输入int key; // 将 key 的声明移到循环内部ArcNode* p = G->vertices[i].firstarc;while (1) { clearScreen();printf("请输入顶点: %s(%d) 将连接的点 \n(退出:-1; 输入限制: 0 <= num < %d) : ",G->vertices[i].name, i, G->vexnum - 1); scanf("%d", &key);if (key == -1) break;if (key < 0 || key >= G->vexnum) { // 修改条件,确保 key 在有效范围内printf("输入顶点信息错误\n\n");continue;}// 检查两点是否已经相连或者是否是与自身相连if (VertexConnect(*G, i, key) || key == i) {printf("此边已连接!!\n\n");continue; // continue 会跳过后面的代码直接回到 while 循环的开始}// 创建新的邻接点ArcNode* t = (ArcNode*)malloc(sizeof(ArcNode));if (t == NULL) {printf("空间不足\n");exit(1);}t->adjvex = key;t->nextarc = NULL;// 将新的邻接点添加到链表中if (p == NULL) {G->vertices[i].firstarc = t;p = t;}else {p->nextarc = t;p = t;}G->arcnum++;printf("该边添加成功\n\n");}}
}
4. 将图像信息写入文件
// 将邻接表表示的图写入文本文件
void WriteALGraph(ALGraph* G) {// 打开文件用于写入,如果文件打开失败则打印错误信息并退出程序FILE* fp = fopen("Graph.txt", "w");if (fp == NULL) {printf("Graph.txt 文件打开失败!\n");exit(1); // 退出程序,返回错误码1}// 将图的顶点数和边数写入文件fprintf(fp, "%d %d\n", G->vexnum, G->arcnum);// 遍历每个顶点for (int i = 0; i < G->vexnum; i++) {// 写入 "begin" 标记,表示一个顶点的邻接表开始fprintf(fp, "begin ");// 写入顶点的名称,假设名称不会超过20个字符,并使用空格填充至20个字符宽度fprintf(fp, "%20s ", G->vertices[i].name);// 获取当前顶点的第一个邻接点ArcNode* p = G->vertices[i].firstarc;// 遍历当前顶点的所有邻接点while (p != NULL) {// 写入 "to" 标记和邻接点的索引fprintf(fp, "to %d ", p->adjvex);// 如果弧有数据,可以取消以下行的注释,并写入弧的数据// fprintf(fp, "arcdata %d", p->data);// 移动到下一个邻接点p = p->nextarc;}// 写入 "end" 标记,表示一个顶点的邻接表结束fprintf(fp, "end\n");}// 关闭文件fclose(fp);// 打印成功信息printf("写入成功\n");
}
5. 从文件读取图的信息
void ReadGrcph(ALGraph* G) {// 打开文件用于读取,如果文件打开失败则打印错误信息并退出程序FILE* fp = fopen("Graph.txt", "r");if (fp == NULL) {printf("Graph.txt 文件打开失败!\n");exit(1); // 退出程序,返回错误码1}// 初始化图GInitALGraph(G);int cntarcnum = 0; // 用于记录实际读取的边数// 读入顶点数和边数(注意:这里读取的边数可能会被后续的实际边数覆盖)fscanf(fp, "%d %d", &G->vexnum, &G->arcnum);// 遍历每个顶点,读取其邻接信息for (int i = 0; i < G->vexnum && !feof(fp); i++) {char temps[20];fscanf(fp, "%s", temps);// 检查是否为"begin"标记,如果不是则报错并退出if (strcmp(temps, "begin") != 0) {printf("文件信息错误!!!\n");fclose(fp);exit(1);}// 读取顶点名称fscanf(fp, "%s", G->vertices[i].name);ArcNode* p = G->vertices[i].firstarc; // 指向当前顶点的首条邻接边// 读取当前顶点的所有邻接信息,直到遇到"end"标记while (1) {fscanf(fp, "%s", temps);if (strcmp(temps, "end") == 0) {break; // 遇到"end"标记,结束当前顶点的邻接信息读取}else if (strcmp(temps, "to") != 0) {// 如果不是"to"标记,则报错并退出printf("文件信息错误!!!\n");fclose(fp);exit(1);}int key = 0; // 用于存储邻接顶点的索引fscanf(fp, "%d", &key); // 读取邻接顶点的索引// 检查索引的有效性,避免越界和自环if (key < 0 || key >= G->vexnum) {printf("文件信息错误!!!\n");fclose(fp);exit(1);}// 检查两点是否已经相连或者是否是与自身相连,如果是则跳过if (VertexConnect(*G, i, key) || key == i) {continue; // 跳过当前循环迭代,继续检查下一个邻接信息}// 创建新的邻接点并添加到链表中ArcNode* t = (ArcNode*)malloc(sizeof(ArcNode));if (t == NULL) {fclose(fp);printf("空间不足\n");exit(1);}t->adjvex = key; // 设置邻接点的目标顶点索引t->nextarc = NULL; // 新节点为链表末尾,nextarc 指向 NULL// 将新节点添加到当前顶点的邻接链表末尾if (p == NULL) {G->vertices[i].firstarc = t; // 如果当前顶点无邻接边,则新边为首条边p = t; // 更新指针p,使其指向新添加的节点}else {p->nextarc = t; // 将新节点添加到链表末尾p = t; // 更新指针p,使其指向新添加的节点}cntarcnum++; // 更新实际读取的边数// 注释掉的代码是之前添加邻接点的另一种方式,与当前方式重复(调试中好像不能正常运行),因此被注释掉/*if (G->vertices[i].firstarc == NULL) {G->vertices[i].firstarc = t;}else {while (p->nextarc != NULL) {p = p->nextarc;}p->nextarc = t;}*/}}// 更新图的边数为实际读取的边数G->arcnum = cntarcnum;// 关闭文件fclose(fp);
}
6. 查看图的信息
// 定义一个函数,用于监视(遍历并显示)图的邻接表表示
void WatchGraph(ALGraph G) {ArcNode* p = NULL; // 定义一个指向边的指针,初始化为NULLint key = 0; // 定义一个变量,用于存储当前正在查看的顶点编号,初始化为0// 使用无限循环来不断显示顶点信息,直到用户选择退出while (1) {clearScreen(); // 清除屏幕,以便显示新的信息// 获取当前顶点的第一条相邻边p = G.vertices[key].firstarc;// 显示图的基本信息和当前顶点的信息printf("顶点数:%d 边数:%d \n", G.vexnum, G.arcnum);printf("当前顶点:%d\n", key);printf("与之相连的边(编号):\n");// 遍历当前顶点的所有相邻边,并打印相邻顶点的编号for (; p != NULL; p = p->nextarc) {printf("%d ", p->adjvex);}// 提示用户输入要跳转的顶点编号,或输入-1退出printf("\n\n跳转顶点 退出:-1(0 <= k <= %d):", G.vexnum - 1);int k;scanf("%d", &k); // 读取用户输入的顶点编号// 再次清除屏幕,以防之前的输入或错误信息干扰接下来的显示clearScreen();// 根据用户输入进行处理if (k == -1) {// 如果用户输入-1,则退出循环break;}else if (k < 0 || k >= G.vexnum) {// 如果用户输入的编号无效,则显示错误信息,并继续显示当前顶点printf("\n\n输入错误,当前顶点将不变\n\n");continue; // 跳过循环的剩余部分,继续下一次迭代}else {// 如果用户输入了有效的顶点编号,则更新当前顶点编号key = k;}}
}
7. 有向图转换为无向图
// 将图转换为无向图
void ConvertUndirectedGraph(ALGraph G, ALGraph* UG) {*UG = G;// int arccnt = 0;for (int i = 0; i < UG->vexnum; i++) {ArcNode* p = UG->vertices[i].firstarc;for (p; p != NULL; p = p->nextarc) {// 查看两点是否相连if (!VertexConnect(*UG, p->adjvex, i)) {// 若没有相连,则连接上ConnectAB(UG, p->adjvex, i);UG->arcnum++;// printf("%d to %d\n", p->adjvex, i);}}}// 从新计算边的数量/*for (int i = 0; i < UG->vexnum; i++) {ArcNode* p = UG->vertices[i].firstarc;for (p; p != NULL; p = p->nextarc) {arccnt++;}}*/// 调整无向图中边的计数。UG->arcnum = UG->arcnum / 2;
}
8.文件内容(参考)// 无向图
4 4
begin adfsgvb to 1 to 3 end
begin dca2 to 2 to 0 end
begin fdgfch to 3 to 1 end
begin afcf to 0 to 2 end
四、总代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#pragma warning(disable:4996);// 清屏
void clearScreen() {system("cls");
}#define MaxVertexNum 100
// 定义顶点对应的数据类型,这里使用字符类型来表示顶点
typedef char VertexType;// 定义边对应的数据类型,这里使用整数类型来表示边的权重(如果有的话)
typedef int EdgeType;// 定义邻接表中的弧(边)节点结构
typedef struct ArcNode {int adjvex; // adjvex 表示该弧所指向的顶点的位置(索引)struct ArcNode* nextarc; // nextarc 指向下一条与该顶点相连的弧的指针,形成链表// EdgeType data; // 边的数据(如权重)如果需要使用边的权重,取消注释
} ArcNode; // 弧节点结构定义结束// 定义邻接表中的顶点节点结构
typedef struct VNode {VertexType name[20]; // name 存储顶点的名称或标识符,这里假设最长为19个字符加上一个结束符ArcNode* firstarc; // firstarc 指向该顶点的第一条相连的弧的指针,即该顶点的邻接链表的头指针
} VNode, AdjList[MaxVertexNum]; // VNode 和 AdjList 是同义类型,AdjList 是一个顶点数组类型,其大小由 MaxVertexNum 决定// 定义图的结构,使用邻接表表示
typedef struct {int vexnum, arcnum; // vexnum 表示图的顶点数,arcnum 表示图的边数AdjList vertices; // vertices 是邻接表,存储了图的所有顶点及其邻接链表
} ALGraph;// 初始化图的邻接表表示的函数
void InitALGraph(ALGraph* G) {// 初始化图的边数为0G->arcnum = 0;// 初始化图的顶点数为0G->vexnum = 0;// 遍历顶点数组,初始化每个顶点的邻接链表为空(即每个顶点的 firstarc 指针指向 NULL)for (int i = 0; i < MaxVertexNum; i++) {G->vertices[i].firstarc = NULL;}
}// 判断在有向图 G 中,顶点 a 是否有一条直接指向顶点 b 的边
bool VertexConnect(ALGraph G, int a, int b) {// 从顶点 a 的邻接链表的头指针开始遍历ArcNode* p = G.vertices[a].firstarc;// 遍历顶点 a 的所有邻接顶点for (; p != NULL; p = p->nextarc) {// 如果当前邻接顶点的位置(索引)等于 b,则返回 true,表示 a 指向 bif (p->adjvex == b) {return true;}}// 如果遍历完所有邻接顶点都没有找到指向 b 的边,则返回 falsereturn false;
}// 将图中顶点 a 与顶点 b 相连
void ConnectAB(ALGraph* G, int a, int b) {ArcNode* p, * t; // 声明两个指针,p 用于遍历,t 用于存储新创建的邻接点// 初始化指针 p,指向顶点 a 的首条邻接边p = G->vertices[a].firstarc;// 为新邻接点分配内存// 创建新的邻接点t = (ArcNode*)malloc(sizeof(ArcNode)); // 分配内存给新节点// 检查内存分配是否成功if (t == NULL) {printf("空间不足\n"); // 打印错误信息exit(1); // 内存分配失败,退出程序}// 设置新邻接点的信息t->adjvex = b; // 设置新节点的目标顶点索引为 bt->nextarc = NULL; // 新节点为链表末尾,nextarc 指向 NULL// 若顶点 a 无邻接边,则新边为首条边if (p == NULL) {G->vertices[a].firstarc = t; // 将新节点设置为顶点 a 的首条邻接边return; // 添加完成,直接返回}// 若顶点 a 有邻接边,则将新边添加到邻接链表末尾// 将 p 调整到最后一条边while (p->nextarc != NULL) { // 遍历至链表末尾p = p->nextarc; // 移动指针至下一条边}// 将新边添加到顶点 a 的邻接链表末尾p->nextarc = t; // 将链表的最后一个节点的 nextarc 指向新节点}// 通过用户输入创建图
void StructGraph(ALGraph* G) {// 初始化数据G->arcnum = 0;G->vexnum = 0;// 假设 MaxVertexNum 已经在其他地方定义printf("请输入图的总顶点数(<=%d) : ", MaxVertexNum);scanf("%d", &G->vexnum);if (G->vexnum > MaxVertexNum || G->vexnum <= 0) {printf("图的顶点数输入错误!\n");exit(1);}// 初始化顶点数组for (int i = 0; i < G->vexnum; i++) {G->vertices[i].firstarc = NULL;printf("\n请输入第 %d 顶点的名字: ", i);scanf("%s", G->vertices[i].name); // 确保 name 数组足够大以存储输入int key; // 将 key 的声明移到循环内部ArcNode* p = G->vertices[i].firstarc;while (1) { clearScreen();printf("请输入顶点: %s(%d) 将连接的点 \n(退出:-1; 输入限制: 0 <= num < %d) : ",G->vertices[i].name, i, G->vexnum - 1); scanf("%d", &key);if (key == -1) break;if (key < 0 || key >= G->vexnum) { // 修改条件,确保 key 在有效范围内printf("输入顶点信息错误\n\n");continue;}// 检查两点是否已经相连或者是否是与自身相连if (VertexConnect(*G, i, key) || key == i) {printf("此边已连接!!\n\n");continue; // continue 会跳过后面的代码直接回到 while 循环的开始}// 创建新的邻接点ArcNode* t = (ArcNode*)malloc(sizeof(ArcNode));if (t == NULL) {printf("空间不足\n");exit(1);}t->adjvex = key;t->nextarc = NULL;// 将新的邻接点添加到链表中if (p == NULL) {G->vertices[i].firstarc = t;p = t;}else {p->nextarc = t;p = t;}G->arcnum++;printf("该边添加成功\n\n");}}
}// 将邻接表表示的图写入文本文件
void WriteALGraph(ALGraph* G) {// 打开文件用于写入,如果文件打开失败则打印错误信息并退出程序FILE* fp = fopen("Graph.txt", "w");if (fp == NULL) {printf("Graph.txt 文件打开失败!\n");exit(1); // 退出程序,返回错误码1}// 将图的顶点数和边数写入文件fprintf(fp, "%d %d\n", G->vexnum, G->arcnum);// 遍历每个顶点for (int i = 0; i < G->vexnum; i++) {// 写入 "begin" 标记,表示一个顶点的邻接表开始fprintf(fp, "begin ");// 写入顶点的名称,假设名称不会超过20个字符,并使用空格填充至20个字符宽度fprintf(fp, "%20s ", G->vertices[i].name);// 获取当前顶点的第一个邻接点ArcNode* p = G->vertices[i].firstarc;// 遍历当前顶点的所有邻接点while (p != NULL) {// 写入 "to" 标记和邻接点的索引fprintf(fp, "to %d ", p->adjvex);// 如果弧有数据,可以取消以下行的注释,并写入弧的数据// fprintf(fp, "arcdata %d", p->data);// 移动到下一个邻接点p = p->nextarc;}// 写入 "end" 标记,表示一个顶点的邻接表结束fprintf(fp, "end\n");}// 关闭文件fclose(fp);// 打印成功信息printf("写入成功\n");
}void ReadGrcph(ALGraph* G) {// 打开文件用于读取,如果文件打开失败则打印错误信息并退出程序FILE* fp = fopen("Graph.txt", "r");if (fp == NULL) {printf("Graph.txt 文件打开失败!\n");exit(1); // 退出程序,返回错误码1}// 初始化图GInitALGraph(G);int cntarcnum = 0; // 用于记录实际读取的边数// 读入顶点数和边数(注意:这里读取的边数可能会被后续的实际边数覆盖)fscanf(fp, "%d %d", &G->vexnum, &G->arcnum);// 遍历每个顶点,读取其邻接信息for (int i = 0; i < G->vexnum && !feof(fp); i++) {char temps[20];fscanf(fp, "%s", temps);// 检查是否为"begin"标记,如果不是则报错并退出if (strcmp(temps, "begin") != 0) {printf("文件信息错误!!!\n");fclose(fp);exit(1);}// 读取顶点名称fscanf(fp, "%s", G->vertices[i].name);ArcNode* p = G->vertices[i].firstarc; // 指向当前顶点的首条邻接边// 读取当前顶点的所有邻接信息,直到遇到"end"标记while (1) {fscanf(fp, "%s", temps);if (strcmp(temps, "end") == 0) {break; // 遇到"end"标记,结束当前顶点的邻接信息读取}else if (strcmp(temps, "to") != 0) {// 如果不是"to"标记,则报错并退出printf("文件信息错误!!!\n");fclose(fp);exit(1);}int key = 0; // 用于存储邻接顶点的索引fscanf(fp, "%d", &key); // 读取邻接顶点的索引// 检查索引的有效性,避免越界和自环if (key < 0 || key >= G->vexnum) {printf("文件信息错误!!!\n");fclose(fp);exit(1);}// 检查两点是否已经相连或者是否是与自身相连,如果是则跳过if (VertexConnect(*G, i, key) || key == i) {continue; // 跳过当前循环迭代,继续检查下一个邻接信息}// 创建新的邻接点并添加到链表中ArcNode* t = (ArcNode*)malloc(sizeof(ArcNode));if (t == NULL) {fclose(fp);printf("空间不足\n");exit(1);}t->adjvex = key; // 设置邻接点的目标顶点索引t->nextarc = NULL; // 新节点为链表末尾,nextarc 指向 NULL// 将新节点添加到当前顶点的邻接链表末尾if (p == NULL) {G->vertices[i].firstarc = t; // 如果当前顶点无邻接边,则新边为首条边p = t; // 更新指针p,使其指向新添加的节点}else {p->nextarc = t; // 将新节点添加到链表末尾p = t; // 更新指针p,使其指向新添加的节点}cntarcnum++; // 更新实际读取的边数// 注释掉的代码是之前添加邻接点的另一种方式,与当前方式重复(调试中好像不能正常运行),因此被注释掉/*if (G->vertices[i].firstarc == NULL) {G->vertices[i].firstarc = t;}else {while (p->nextarc != NULL) {p = p->nextarc;}p->nextarc = t;}*/}}// 更新图的边数为实际读取的边数G->arcnum = cntarcnum;// 关闭文件fclose(fp);
}// 定义一个函数,用于监视(遍历并显示)图的邻接表表示
void WatchGraph(ALGraph G) {ArcNode* p = NULL; // 定义一个指向边的指针,初始化为NULLint key = 0; // 定义一个变量,用于存储当前正在查看的顶点编号,初始化为0// 使用无限循环来不断显示顶点信息,直到用户选择退出while (1) {clearScreen(); // 清除屏幕,以便显示新的信息// 获取当前顶点的第一条相邻边p = G.vertices[key].firstarc;// 显示图的基本信息和当前顶点的信息printf("顶点数:%d 边数:%d \n", G.vexnum, G.arcnum);printf("当前顶点:%d\n", key);printf("与之相连的边(编号):\n");// 遍历当前顶点的所有相邻边,并打印相邻顶点的编号for (; p != NULL; p = p->nextarc) {printf("%d ", p->adjvex);}// 提示用户输入要跳转的顶点编号,或输入-1退出printf("\n\n跳转顶点 退出:-1(0 <= k <= %d):", G.vexnum - 1);int k;scanf("%d", &k); // 读取用户输入的顶点编号// 再次清除屏幕,以防之前的输入或错误信息干扰接下来的显示clearScreen();// 根据用户输入进行处理if (k == -1) {// 如果用户输入-1,则退出循环break;}else if (k < 0 || k >= G.vexnum) {// 如果用户输入的编号无效,则显示错误信息,并继续显示当前顶点printf("\n\n输入错误,当前顶点将不变\n\n");continue; // 跳过循环的剩余部分,继续下一次迭代}else {// 如果用户输入了有效的顶点编号,则更新当前顶点编号key = k;}}
}// 将图转换为无向图
void ConvertUndirectedGraph(ALGraph G, ALGraph* UG) {*UG = G;// int arccnt = 0;for (int i = 0; i < UG->vexnum; i++) {ArcNode* p = UG->vertices[i].firstarc;for (p; p != NULL; p = p->nextarc) {// 查看两点是否相连if (!VertexConnect(*UG, p->adjvex, i)) {// 若没有相连,则连接上ConnectAB(UG, p->adjvex, i);UG->arcnum++;// printf("%d to %d\n", p->adjvex, i);}}}// 从新计算边的数量/*for (int i = 0; i < UG->vexnum; i++) {ArcNode* p = UG->vertices[i].firstarc;for (p; p != NULL; p = p->nextarc) {arccnt++;}}*/// 调整无向图中边的计数。UG->arcnum = UG->arcnum / 2;
}int main() {ALGraph G;InitALGraph(&G);//StructGraph(&G);//WriteALGraph(&G);ReadGrcph(&G);ConvertUndirectedGraph(G, &G);//WatchGraph(G);WriteALGraph(&G);//WatchGraph(G);return 0;
}
相关文章:
数据结构-图-领接表存储
一、了解图的领接表存储 1、定义与结构 定义:邻接表是图的一种链式存储结构,它通过链表将每个顶点与其相邻的顶点连接起来。 结构: 顶点表:通常使用一个数组来存储图的顶点信息,数组的每个元素对应一个顶点ÿ…...

快速入门web安全
一.确定初衷 1.我真的喜欢搞安全吗? 2.我只是想通过安全赚钱钱吗? 3.我不知道做什么就是随便。 4.一辈子做信息安全吗 这些不想清楚会对你以后的发展很不利,与其盲目的学习web安全,不如先做一个长远的计划。 否则在我看来都是浪费时间。如果你考虑好了…...

rabbitMq两种消费应答失败处理方式
在rabbitMq消费端,有三种应答模式: none:不处理。即消息投递给消费者后立刻 ack 消息会立刻从MQ删除。非常不安全,不建议使用 manual:手动模式。需要自己在业务代码中调用api,发送 ack 或 rejectÿ…...

Qt C++(一) 5.12安装+运行第一个项目
安装 1. Download Qt OSS: Get Qt Online Installer 在该链接中下载qt在线安装程序 2. 安装时候,注意关键一步,archive是存档的意思,可以找到旧的版本, 比如5.12 3. 注意组件没必要全选,否则需要安装50个g, 经过请教…...
【RISC-V CPU Debug 专栏 1 -- RISC-V debug 规范】
文章目录 RISC-V Debug调试用例支持的功能限制和不包括的内容RISC-V 调试架构的主要组件用户与调试主机调试翻译器调试传输硬件调试传输模块(DTM)调试模块(DM)调试功能触发模块版本介绍RISC-V Debug RISC-V 调试规范为 RISC-V 处理器提供了一套标准化的调试接口和功能,旨…...
使用Gradle编译前端的项目
使用Gradle编译前端的项目 前言项目结构根项目(parent-project)的 settings.gradle.kts后端项目(backend)的 build.gradle.kts前端项目(frontend)的 build.gradle.kts打包bootJar 前言 最近的项目都是使用…...

网络爬虫——常见问题与调试技巧
在开发网络爬虫的过程中,开发者常常会遇到各种问题,例如网页加载失败、数据提取错误、反爬机制限制等。以下内容将结合实际经验和技术方案,详细介绍解决常见错误的方法,以及如何高效调试和优化爬虫代码。 1. 爬虫过程中常见的错误…...

【AI绘画】Midjourney进阶:色调详解(下)
博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AI绘画 | Midjourney 文章目录 💯前言💯Midjourney中的色彩控制为什么要控制色彩?为什么要在Midjourney中控制色彩? 💯色调纯色调灰色调暗色调 💯…...
springboot+redis+lua实现分布式锁
1 分布式锁 Java锁能保证一个JVM进程里多个线程交替使用资源。而分布式锁保证多个JVM进程有序交替使用资源,保证数据的完整性和一致性。 分布式锁要求 互斥。一个资源在某个时刻只能被一个线程访问。避免死锁。避免某个线程异常情况不释放资源,造成死锁…...

【Petri网导论学习笔记】Petri网导论入门学习(十一) —— 3.3 变迁发生序列与Petri网语言
目录 3.3 变迁发生序列与Petri网语言定义 3.4定义 3.5定义 3.6定理 3.5例 3.9定义 3.7例 3.10定理 3.6定理 3.7 有界Petri网泵引理推论 3.5定义 3.9定理 3.8定义 3.10定义 3.11定义 3.12定理 3.93.3 变迁发生序列与Petri网语言 对于 Petri 网进行分析的另一种方法是考察网系统…...
docker-compose文件的简介及使用
Docker Compose是Docker官方的开源项目,主要用于定义和运行多容器Docker应用。以下是对Docker Compose的详细介绍: 一、主要功能: 容器编排:Docker Compose允许用户通过一个单独的docker-compose.yml模板文件(YAML格…...

[护网杯 2018]easy_tornado
这里有一个hint点进去看看,他说md5(cookie_secretmd5(filename)),所以我们需要获得cookie_secret的value 根据题目tornado,它可能是tornado的SSTI 这里吧filehash改为NULL. 是tornado的SSTI 输入{{handler.settings}} (settings 属性是一个字典&am…...

基于STM32的智能风扇控制系统
基于STM32的智能风扇控制系统 持续更新,欢迎关注!!! ** 基于STM32的智能风扇控制系统 ** 近几年,我国电风扇市场发展迅速,产品产出持续扩张,国家产业政策鼓励电风扇产业向高技术产品方向发展,国内企业新增投资项目投…...
决策树——基于乳腺癌数据集与cpu数据集实现
决策树——乳腺癌数据实现 4.1 训练决策树模型,并计算测试集的准确率 1. 读入数据 from sklearn import datasets from sklearn.tree import DecisionTreeClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import confusion_matrix …...
探索空间自相关:揭示地理数据中的隐藏模式
目录 一、什么是空间自相关? 类型 二、空间自相关的数学基础 空间加权矩阵 三、度量空间自相关的方法 1. 全局自相关 2. 局部自相关 四、空间自相关的实际应用 五、Python实现空间自相关分析 1. 数据准备 2. 计算莫兰指数 3. 局部自相关(LISA 分析&…...

echarts使用示例
柱状图折线图 折柱混合:https://echarts.apache.org/examples/zh/editor.html?cmix-line-bar option {title:{show: true},tooltip: {trigger: axis,axisPointer: {type: cross,crossStyle: {color: #999}}},toolbox: {feature: {dataView: { show: true, readOnl…...

Flink高可用配置(HA)
从Flink架构中我们可以看到,JobManager这个组件非常重要,是中心协调器,负责任务调度和资源管理。默认情况下,每个Flink集群只有一个JobManager实例。这会产生单点故障(SPOF):如果JobManager崩溃,则无法提交新程序,正在运行的程序也会失败。通过JobManager的高可用性,…...

如何编写出色的技术文档
目录 编辑 1. 明确文档目的和受众 目的的重要性 了解受众 2. 收集和组织信息 信息收集的技巧 组织信息 3. 规划文档结构 结构规划的重要性 结构规划的步骤 4. 编写内容 语言和风格 内容的组织 编写技巧 5. 审阅和测试 审阅的重要性 测试的必要性 6. 版本控…...

学习日记_20241126_聚类方法(谱聚类Spectral Clustering)
前言 提醒: 文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。 其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展…...

图书系统小案例
目前就实现了分页查询,修改,删除功能 这个小案例练习到了很多技能,比如前后端交互、异步请求、三层架构思想、后端连接数据库、配置文件、基础业务crud等等 感兴趣的小伙伴可以去做一个试试 准备工作 1、使用maven构建一个web工程 打开i…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...

密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...

鸿蒙Navigation路由导航-基本使用介绍
1. Navigation介绍 Navigation组件是路由导航的根视图容器,一般作为Page页面的根容器使用,其内部默认包含了标题栏、内容区和工具栏,其中内容区默认首页显示导航内容(Navigation的子组件)或非首页显示(Nav…...