【数据结构第 6 章 ③】- 用 C 语言实现邻接表并简单介绍十字链表和邻接多重表

目录

一、邻接表

1.1 - ALGraph.h

1.2 - ALGraph.c

1.3 - Test.c

二、十字链表

三、邻接多重表


 


一、邻接表

邻接表(Adjacency List)是图的一种链式存储结构。在邻接表中,对图中每个顶点建立一个单链表,第 i 个单链表中的结点表示依附于顶点 vi 的边(对有向图是以顶点 vi 为尾的弧)。每个结点由 3 个域组成,其中邻接顶点域(adjVexPos)指示与顶点 vi 邻接的顶点在图中的位置,链域(nextEdge)指示下一条边(或弧)的结点;数据域(info)存储和边(或弧)相关的信息,如权值等

在表头结点中,除了设有链域(firstEdge)指向链表中的第一个结点之外,还设有存储顶点 vi 的名称或其他有关信息的数据域(vertex)。这些表头结点通常以顺序结构的形式存储,以便随机访问任一顶点的边链表。

下图 (a) 和 (b) 所示为图一中 G1 和 G2 的邻接表。

在无向图的邻接表中,顶点 vi 的度恰为第 i 个链表中的结点数;而在有向图中,第 i 个链表中的结点个数只是顶点 vi 的出度,为求入度,必须遍历整个邻接表。在所有链表中,其邻接顶点域的值为 i 的结点个数是顶点 vi 的入度。有时,为了便于确定顶点的入度,可以建立一个有向图的逆邻接表,即对每个顶点 vi 建立一个链接所有进入 vi 的边的表。

下图 (c) 所示为图一中有向图 G1 的逆邻接表。

1.1 - ALGraph.h

注意:下面是以无向图为例的

#pragma once#define DEFAULT_CAPACITY 10typedef char VertexType;typedef struct EdgeNode
{int adjVexPos;struct EdgeNode* nextEdge;
}EdgeNode;typedef struct VertexNode
{VertexType vertex;EdgeNode* firstEdge;
}VertexNode;typedef struct ALGraph
{VertexNode* vertices;int vSize;int eSize;int capacity;
}ALGraph;// 基本操作
void ALGraphInit(ALGraph* pg);  // 初始化void ShowAdjList(ALGraph* pg);  // 显示邻接表int GetVertexPos(ALGraph* pg, VertexType v);  // 获取顶点的位置void InsertVertex(ALGraph* pg, VertexType v);  // 插入顶点
void InsertEdge(ALGraph* pg, VertexType v1, VertexType v2);  // 插入边void EraseVertex(ALGraph* pg, VertexType v);  // 删除顶点
void EraseEdge(ALGraph* pg, VertexType v1, VertexType v2);  // 删除边int GetFirstAdjVexPos(ALGraph* pg, VertexType v);  // 获取 v 的第一个邻接顶点的位置
int GetNextAdjVexPos(ALGraph* pg, VertexType v, VertexType w);
// 获取 v 的(相对于 w)的下一个邻接顶点的位置void ALGraphDestroy(ALGraph* pg);  // 销毁

1.2 - ALGraph.c

  1. 初始化

    void ALGraphInit(ALGraph* pg)
    {assert(pg);pg->vSize = pg->eSize = 0;pg->capacity = DEFAULT_CAPACITY;pg->vertices = (VertexNode*)malloc(sizeof(VertexNode) * pg->capacity);assert(pg->vertices);for (int i = 0; i < pg->capacity; ++i){pg->vertices[i].firstEdge = NULL;}
    }
  2. 显示邻接表

    void ShowAdjList(ALGraph* pg)
    {assert(pg);for (int i = 0; i < pg->vSize; ++i){printf("%d %c:>", i, pg->vertices[i].vertex);EdgeNode* cur = pg->vertices[i].firstEdge;while (cur){printf("%d-->", cur->adjVexPos);cur = cur->nextEdge;}printf("NULL\n");}
    }
  3. 获取顶点的位置

    int GetVertexPos(ALGraph* pg, VertexType v)
    {assert(pg);for (int i = 0; i < pg->vSize; ++i){if (pg->vertices[i].vertex == v)return i;}return -1;
    }
  4. 插入顶点

    void InsertVertex(ALGraph* pg, VertexType v)
    {assert(pg);// 考虑是否需要扩容if (pg->vSize == pg->capacity){VertexNode* tmp = (VertexNode*)realloc(pg->vertices, sizeof(VertexNode) * 2 * pg->capacity);assert(tmp);pg->vertices = tmp;for (int i = pg->capacity; i < 2 * pg->capacity; ++i){pg->vertices[i].firstEdge = NULL;}pg->capacity *= 2;}// 插入顶点pg->vertices[pg->vSize++].vertex = v;
    }
  5. 插入边

    void InsertEdge(ALGraph* pg, VertexType v1, VertexType v2)
    {assert(pg);int pos1 = GetVertexPos(pg, v1);int pos2 = GetVertexPos(pg, v2);if (pos1 == -1 || pos2 == -1)return;// 插入 (v1, v2)EdgeNode* p = (EdgeNode*)malloc(sizeof(EdgeNode));assert(p);p->adjVexPos = pos2;// 头插p->nextEdge = pg->vertices[pos1].firstEdge;pg->vertices[pos1].firstEdge = p;// 插入 (v2, v1)p = (EdgeNode*)malloc(sizeof(EdgeNode));assert(p);p->adjVexPos = pos1;// 头插p->nextEdge = pg->vertices[pos2].firstEdge;pg->vertices[pos2].firstEdge = p;++pg->eSize;  // 注意:边数只需要加 1
    }
  6. 删除顶点

    // 删除顶点
    void EraseVertex(ALGraph* pg, VertexType v)
    {assert(pg);int pos = GetVertexPos(pg, v);if (pos == -1)return;// 第一步:删除和顶点 v 相关联的边EdgeNode* cur = pg->vertices[pos].firstEdge;while (cur){// 找到邻接顶点 v2, 先删除 (v2, v)int pos2 = cur->adjVexPos;EdgeNode* prev2 = NULL;EdgeNode* cur2 = pg->vertices[pos2].firstEdge;while (cur2 && cur2->adjVexPos != pos){prev2 = cur2;cur2 = cur2->nextEdge;}if (cur2){if (prev2 == NULL)pg->vertices[pos2].firstEdge = cur2->nextEdge;elseprev2->nextEdge = cur2->nextEdge;free(cur2);}// 再删除 (v, v2)pg->vertices[pos].firstEdge = cur->nextEdge;free(cur);cur = pg->vertices[pos].firstEdge;--pg->eSize;  // 注意不要遗漏}// 第二步:删除顶点 vpg->vertices[pos].vertex = pg->vertices[pg->vSize - 1].vertex;pg->vertices[pos].firstEdge = pg->vertices[pg->vSize - 1].firstEdge;cur = pg->vertices[pos].firstEdge;while (cur){int pos2 = cur->adjVexPos;EdgeNode* cur2 = pg->vertices[pos2].firstEdge;while (cur2){if (cur2->adjVexPos == pg->vSize - 1){cur2->adjVexPos = pos;break;}cur2 = cur2->nextEdge;}cur = cur->nextEdge;}--pg->vSize;  // 注意不要遗漏
    }
  7. 删除边

    void EraseEdge(ALGraph* pg, VertexType v1, VertexType v2)
    {assert(pg);int pos1 = GetVertexPos(pg, v1);int pos2 = GetVertexPos(pg, v2);if (pos1 == -1 || pos2 == -1)return;// 删除 (v1, v2)EdgeNode* prev = NULL;EdgeNode* cur = pg->vertices[pos1].firstEdge;while (cur && cur->adjVexPos != pos2){prev = cur;cur = cur->nextEdge;}if (cur == NULL)  // 说明 (v1, v2) 不存在 return;if (prev == NULL)pg->vertices[pos1].firstEdge = cur->nextEdge;elseprev->nextEdge = cur->nextEdge;free(cur);// 删除 (v2, v1)// 注意:此时 (v2, v1) 一定存在prev = NULL;cur = pg->vertices[pos2].firstEdge;while (cur->adjVexPos != pos1){prev = cur;cur = cur->nextEdge;}if (prev == NULL)pg->vertices[pos2].firstEdge = cur->nextEdge;elseprev->nextEdge = cur->nextEdge;free(cur);--pg->eSize;  // 注意不要遗漏
    }
  8. 获取 v 的第一个邻接顶点的位置

    int GetFirstAdjVexPos(ALGraph* pg, VertexType v)
    {assert(pg);int pos = GetVertexPos(pg, v);if (pos == -1)return -1;EdgeNode* cur = pg->vertices[pos].firstEdge;if (cur)return cur->adjVexPos;elsereturn -1;
    }
  9. 获取 v 的(相对于 w)的下一个邻接顶点的位置

    int GetNextAdjVexPos(ALGraph* pg, VertexType v, VertexType w)
    {assert(pg);int pos1 = GetVertexPos(pg, v);int pos2 = GetVertexPos(pg, w);if (pos1 == -1 || pos2 == -1)return -1;EdgeNode* cur = pg->vertices[pos1].firstEdge;while (cur && cur->adjVexPos != pos2){cur = cur->nextEdge;}if (cur && cur->nextEdge)return cur->nextEdge->adjVexPos;elsereturn -1;
    }
  10. 销毁

    void ALGraphDestroy(ALGraph* pg)
    {assert(pg);for (int i = 0; i < pg->vSize; ++i){EdgeNode* cur = pg->vertices[i].firstEdge;while (cur){// 头删pg->vertices[i].firstEdge = cur->nextEdge;free(cur);cur = pg->vertices[i].firstEdge;}}free(pg->vertices);pg->vertices = NULL;pg->vSize = pg->eSize = pg->capacity = 0;
    }

1.3 - Test.c

#include "ALGraph.h"
#include <stdio.h>int main()
{ALGraph g;ALGraphInit(&g);InsertVertex(&g, 'A');InsertVertex(&g, 'B');InsertVertex(&g, 'C');InsertVertex(&g, 'D');InsertVertex(&g, 'E');InsertEdge(&g, 'A', 'B');InsertEdge(&g, 'A', 'D');InsertEdge(&g, 'B', 'C');InsertEdge(&g, 'B', 'E');InsertEdge(&g, 'C', 'D');InsertEdge(&g, 'C', 'E');ShowAdjList(&g);printf("\n");EraseVertex(&g, 'C');ShowAdjList(&g);printf("\n");EraseEdge(&g, 'A', 'B');ShowAdjList(&g);printf("\n");printf("%d\n", GetFirstAdjVexPos(&g, 'A'));  // 3printf("%d\n", GetNextAdjVexPos(&g, 'A', 'D'));  // -1ALGraphDestroy(&g);return 0;
}


二、十字链表

十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以看成是将有向图的邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中的每一条弧有一个结点,对应于每个顶点也有一个结点。这些结点的结构如下图所示。

在弧结点中有 5 个域:其中尾域(tailvex)头域(headvex)分别指示弧尾和弧头这两个顶点在图中的位置,链域 hlink 指向弧头相同的下一条弧,而链域 tlink 指向弧尾相同的下一条弧,info 域指向该弧的相关信息。弧头相同的弧在同一链表上,弧尾相同的弧也在同一链表上

它们的头结点即为顶点结点,它由 3 个域组成:其中 data 域存储和顶点相关的信息,如顶点的名称等;firstinfirstout 为两个链域,分别指向以该顶点为弧头或弧尾的第一个弧结点。

例如下图 (a) 中所示的图的十字链表如下图 (b) 所示。


三、邻接多重表

邻接多重表(Adjacency Multilist)是无向图的另一种链式存储结构。虽然邻接表是无向图的一种有效的存储结构,在邻接表中容易求得顶点和边的各种信息,但是在邻接表中每一条边 有两个结点,分别在第 i 个和第 j 个链表中,这给某些图的操作带来不便,例如在某些图的应用问题中需要对边进行某种操作,如对已被搜索过的边做记号或删除一条边等,此时需要找到表示同一条边的两个结点。因此,在进行这一类操作的无向图的问题中采用邻接多重表更为适宜。

邻接多重表的结构和十字链表类似,在邻接多重表中,每一条边用一个结点表示,它由下图 (a) 所示的 6 个域组成。其中 mark 为标志域,可用以标记该条边是否被搜索过;ivexjvex 为该边依附的两个顶点在图中的位置;ilink 指向下一条依附于顶点 ivex 的边;jlink 指向下一条依附于顶点 jvex 的边,info 为指向和边相关的各种信息的指针域。

每个顶点也用一个结点表示,它由下图 (b) 所示的 2 个域组成。其中,data 域存储和该顶点相关的信息;firstedge 域指示第一条依附于该顶点的边。

例如下图所示为图一中无向图 G2 的邻接多重表。

在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。可见,对于无向图而言,其邻接多重表和邻接表的差别,仅仅在于同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。因此,除了在边结点中增加一个标志域外,邻接多重表所需的存储量和邻接表相同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/213917.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

32.768KHz时钟RTC晶振精度PPM值及频差计算

一个数字电路就像一所城市的交通&#xff0c;晶振的作用就是十字路口的信号灯&#xff0c;因此晶振的品质及其电路应用尤其关键。数字电路又像生命体&#xff0c;它的运行就像人身体里的血液流通&#xff0c;它不是由单一的某个器件或器件单元构成&#xff0c;而是由多个器件及…

Rust的From与Into Trait

Into的本质是调用了From Trait 的方法。 From是底层的方法&#xff0c;把From实现了&#xff0c;Into的实现&#xff0c;编译器会自动根据From Trait生成Into Trait的代码 编译器自动类型推导出Into Trait的U的类型&#xff0c;调用了U类型的From的方法&#xff0c;实现其他类…

3DMAX UV贴图修改插件安装卸载方法

3DMAX UV贴图修改插件安装卸载方法 3dMax贴图修改插件PolyUnwrapper是为纹理艺术家设计的一整套专业工具&#xff0c;尤其适用于建筑和游戏行业。 它包含许多功能&#xff0c;将大大帮助您改进UV展开的工作流程。 【主要功能特点】 -多重缝合。一次缝合多个壳 -自定义打包算…

亚马逊运营推荐数仓项目实战

亚马逊运营推荐数仓项目实战 项目技术栈 HadoopSpark (Python)Scala SparkSQLSparkStreaming MongoDB Redis Kafka Flume ( SpringMVC vue) 1 项目介绍 1.1 项目系统架构 项目以推荐系统建设领域知名的经过修改过的中文亚马逊电商数据集作为依托&#xff0c;以某电商…

【小白专用】在 vs 中使用 nuget 安装NPOI

C#操作Excel有多种方法&#xff0c;如通过数据库的方式来读写Excel的OleDb方式&#xff0c;但是OleDb方式需要安装微软office&#xff0c;还可以通过COM组件方式操作Excel&#xff0c;也需要安装微软Excel。如果不想安装微软办公套餐可以使用ClosedXML、EPPlus、NPOI。本文主要…

Ubuntu22.04 LTS + CUDA12.3 + CUDNN8.9.7 + PyTorch2.1.1

简介 本文记录Ubuntu22.04长期支持版系统下的CUDA驱动和cuDNN神经网络加速库的安装&#xff0c;并安装PyTorch2.1.1来测试是否安装成功。 安装Ubuntu系统 如果是旧的不支持UEFI启动的主板&#xff0c;请参考本人博客U盘系统盘制作与系统安装&#xff08;详细图解&#xff09…

mac苹果电脑清除数据软件CleanMyMac X4.16

在数字时代&#xff0c;保护个人隐私变得越来越重要。当我们出售个人使用的电脑&#xff0c;亦或者离职后需要上交电脑&#xff0c;都需要对存留在电脑的个人信息做彻底的清除。随着越来越多的人选择使用苹果电脑&#xff0c;很多人想要了解苹果电脑清除数据要怎样做才是最彻底…

深入理解Sentinel系列-1.初识Sentinel

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

低多边形游戏风格3D模型纹理贴图

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时&#xff0c;有几种不同的风格&#xf…

《安富莱嵌入式周报》第328期:自主微型机器人,火星探测器发射前失误故障分析,微软推出12周24期免费AI课程,炫酷3D LED点阵设计,MDK5.39发布

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 更新一期视频教程&#xff1a; 【实战技能】 单步运行源码分析&#xff0c;一期视频整明白FreeRTOS内核源码框架和运行…

模电·放大电路的分析方法——图解法

放大电路的分析方法——图解法 静态工作点的分析电压放大倍数的分析波形非线性失真的分析直流负载线与交流负载线图解法的适用范围 在实际测出放大管的输入特性、输出特性和已知放大电路中其它各元件参数的情况下&#xff0c;利用作图的方法对放大电路进行分析即为图解法。 静…

BUU SQL COURSE 1

四 发现有登录框&#xff0c;爆破半天也爆破不出来&#xff0c;只能从别的地方下手了 F12一下 发现了一个传参 进去发现id可以传参&#xff0c;sql注入一下试试 前三个都有回显&#xff0c;当id4的时候页面没有回显了&#xff0c;正好验证 了页面 有三个新闻 当order by 3的时…

CSPNet: A New Backbone that can Enhance Learning Capability of CNN(2019)

文章目录 -Abstract1 Introduction2 Related workformer work 3 Method3.1 Cross Stage Partial Network3.2 Exact Fusion Model 4 Experiments5 Conclusion 原文链接 源代码 - 梯度信息重用&#xff08;有别于冗余的梯度信息&#xff09;可以减少计算量和内存占用提高效率&am…

内存学习——堆(heap)

目录 一、概念二、自定义malloc函数三、Debug运行四、heap_4简单分析4.1 heap管理链表结构体4.2 堆初始化4.3 malloc使用4.4 free使用 一、概念 内存分为堆和栈两部分&#xff1a; 栈&#xff08;Stack&#xff09;是一种后进先出&#xff08;LIFO&#xff09;的数据结构&…

Flutter自定义下拉选择框dropDownMenu

利用PopupMenuButton和PopupMenuItem写了个下拉选择框&#xff0c;之所以不采用系统的&#xff0c;是因为自定义的更能适配项目需求&#xff0c;话不多说&#xff0c;直接看效果 下面直接贴出代码、代码中注释写的都很清楚&#xff0c;使用起来应该很方便&#xff0c;如果有任何…

Navicat 技术指引 | 适用于 GaussDB 分布式的数据生成功能

Navicat Premium&#xff08;16.3.3 Windows 版或以上&#xff09;正式支持 GaussDB 分布式数据库。GaussDB 分布式模式更适合对系统可用性和数据处理能力要求较高的场景。Navicat 工具不仅提供可视化数据查看和编辑功能&#xff0c;还提供强大的高阶功能&#xff08;如模型、结…

得帆云助力容百科技构建CRM系统,实现LTC全流程管理

宁波容百新能源科技股份有限公司 宁波容百新能源科技股份有限公司&#xff08;以下简称“容百科技”&#xff09;于2014年9月建立&#xff0c;是高科技新能源材料行业的跨国型集团公司。专业从事锂电池正极材料的研发、生产和销售&#xff0c;于2019年登陆上交所科创板&#x…

Docker 部署 2FAuth 服务

拉取最新版本的 2FAuth 镜像&#xff1a; $ sudo docker pull 2fauth/2fauth:latest在本地预先创建好 2fauth 目录, 用于映射 2FAuth 容器内的 /2fauth 目录。 使用以下命令, 在 前台 运行 2FAuth 容器: $ sudo docker run -it --rm --name 2fauth -p 10085:8000/tcp -v /ho…

阻抗控制下机器人接触刚性环境振荡不稳定进行阻抗调节

在阻抗控制下&#xff0c;当机器人接触刚性环境时&#xff0c;可能会出现振荡不稳定的情况。这可以通过调整机器人的阻抗参数来进行调节。 阻抗接触 阻抗参数中的质量、阻尼和刚度都会对机器人控制系统的性能和稳定性产生重要影响。质量主要影响系统的惯性&#xff0c;从而影响…

打破常规思维:Scrapy处理豆瓣视频下载的方式

概述 Scrapy是一个强大的Python爬虫框架&#xff0c;它可以帮助我们快速地开发和部署各种类型的爬虫项目。Scrapy提供了许多方便的功能&#xff0c;例如请求调度、数据提取、数据存储、中间件、管道、信号等&#xff0c;让我们可以专注于业务逻辑&#xff0c;而不用担心底层的…