数据结构-图-领接表存储
一、了解图的领接表存储
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…...
目标检测之学习路线(本科版)
以下是为一名计算机科学与技术本科大四学生整理的“目标检测”学习路线,结合了从基础到高级的内容,适合初学者逐步深入。每个阶段都有明确的学习要求、学习建议和资源推荐。 阶段一:基础知识学习 学习要求: 掌握编程语言 Pytho…...
C#调用C++ DLL方法之C++/CLI(托管C++)
托管C与C/CLI前世今生 C/CLI (C/Common Language Infrastructure) 是一种用于编写托管代码的语言扩展,它是为了与 .NET Framework 进行互操作而设计的。C/CLI 是 C 的一种方言,它引入了一些新的语法和关键字,以便更好地支持 .NET 类型和垃圾…...
免费搭建一个属于自己的个性化博客(Hexo+Fluid+Github)
文章目录 0.简介1. 下载安装fluid主题2. 创建文章3. 添加分类及标签3.1 创建“分类”选项3.2 创建“标签”选项4. 文章中插入图片5. 添加阅读量统计6. 添加评论功能7. 显示文章更新时间8. 为hexo添加latex支持小结参考文献0.简介 通过HEXO模板和Fluid主题搭建自己的博客,预览…...
vue3 开发利器——unplugin-auto-import
这玩意儿是干啥的? 还记得 Vue 3 的组合式 API 语法吗?如果有印象,那你肯定对以下代码有着刻入 DNA 般的熟悉: 刚开始写觉得没什么,但是后来渐渐发现,这玩意儿几乎每个页面都有啊! 每次都要写…...
开发需求总结19-vue 根据后端返回一年的数据,过滤出符合条件数据
需求描述: 定义时间分界点:每月26号8点,过了26号8点则过滤出data数组中符合条件数据下个月的数据,否则过滤出当月数据 1.假如现在是2024年11月14日,那么过滤出data数组中日期都是2024-11月的数据; 2.假如…...
人工智能如何改变创新和创造力?
王琼工作室 输出的力量 有了GPT这样的人工智能平台,创新和创造力的机会在哪里? 我们是否有信心: 面对效率,超越效率。 把问题拓展为机会? 把机会拓展为价值? 让智能更好地和我们协作,走心、走…...
Github 基本使用学习笔记
1. 基本概念 1.1 一些名词 Repository(仓库) 用来存放代码,每个项目都有一个独立的仓库。 Star(收藏) 收藏你喜欢的项目,方便以后查看。 Fork(克隆复制项目) 复制别人的仓库&…...
群论入门笔记
群的基本定义 群由一组元素 G 和一个运算(常用符号包括 ,x , 或 ∗)组成。 封闭性 对于任意两个元素 x,y∈G,运算 x * y 的结果仍然属于集合 G,即: ∀x,y∈G,x∗y∈G. 结合律 对于任意 a,b,c∈G&…...
2024最新python使用yt-dlp
2024最新python使用yt-dlp下载YT视频 1.获取yt的cookie1)google浏览器下载Get cookies.txt LOCALLY插件2)导出cookie 2.yt-dlp下载[yt-dlp的GitHub地址](https://github.com/yt-dlp/yt-dlp?tabreadme-ov-file)1)使用Pycharm(2024.3)进行代码…...
Python + 深度学习从 0 到 1(00 / 99)
希望对你有帮助呀!!💜💜 如有更好理解的思路,欢迎大家留言补充 ~ 一起加油叭 💦 欢迎关注、订阅专栏 【深度学习从 0 到 1】谢谢你的支持! ⭐ 什么是深度学习? 人工智能、机器学习与…...
免费做网站公司/投资网站建设方案
这是一道DP题,我写的时候也是尽量往DP了想,最后使用了一种蹩脚的类似DP又不太像是DP的方法写了出来 求最小的移动步数,先得求出一个已知骑士和国王的位置,求出其到其他位置的最小移动步数 国王的位置已知的话,最小步…...
代做计算机毕业设计网站/手机百度提交入口
树立新的课程观念提高教育教学质量<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" />在这段时间里,我们参加了新一轮校本培训。在一学期走过的路,我们开展了许多的探索,也取得了一些阶段性成果&…...
在dw里网站页面列表怎么做/百度app内打开
在web或移动端开发中,有时候我们需要做一个可滚动显示的banner、轮播、滑动翻页显示内容等,常用的插件就数swiper。当然如果我不想因为一个小的页面去引入一个库,那么我们就手动写一个简易版的swiper。因为正做的项目是vue中需要用到滑动翻页…...
wordpress图片自适应主题/运营推广公司
在我们日常开发工作中,有时候会遇到需要把某个git分支中的某个功能合并到另一分支,却因为一些差异而不能使用git merge,进行单纯的分支合并。 这时cherry-pick便将起到至关重要的作用了。 合并单个commit 例:想要在b1分支合并进x功能&#x…...
网站宣传语/百度不收录网站
大数据在各个领域的应用于风云变化的市场无不相关。当前无论是中国经济还是世界经济都处于快速变革期,资本市场随之变得越来越复杂,传统的定性投资方式也不断受到冲击和挑战,量化投资因此受到追捧。 随着互联网、移动互联网、传感器、物联网、…...
为您打造高端品牌网站/优化网站的步骤
国庆期间,数据技术嘉年华为读者准备了做题赢门票的活动,10道极富挑战的Oracle/MySQL/PostgreSQL考题,选择任何一种答对7题即可免费获赠大会门票一张,这里公布一下获奖名单。活动回顾:2020 数据库行业大事件盘点和嘉年华…...