【数据结构】图论存储结构深度解析:邻接多重表如何实现无向图O(1)删边?邻接矩阵/链表/十字链对比

邻接多重表

  • 导读
  • 一、有向图的存储结构
  • 二、邻接多重表
  • 三、存储结构
  • 四、算法评价
    • 4.1 时间复杂度
    • 4.2 空间复杂度
  • 五、四种存储方式的总结
    • 5.1 空间复杂度
    • 5.2 找相邻边
    • 5.3 删除边或结点
    • 5.4 适用于
    • 5.5 表示方式
  • 六、图的基本操作
  • 结语

邻接多重表

导读

大家好,很高兴又和大家见面啦!!!

经过前面的内容,我们已经学习了图的三种存储结构:

  • 邻接矩阵
  • 邻接表
  • 十字链表

在今天的内容中我们将会介绍图的第四种存储结构以及图的一些基本操作。下面我们直接进入今天的内容;

一、有向图的存储结构

在有向图中,我们可以通过3种存储结构来存储有向图的顶点与弧的信息,并且这三种存储结构各有其优缺点:

  • 邻接矩阵
    • 优点:能高效查找两个顶点之间的边,适合存储稠密图
    • 不足:会浪费大量的存储空间
  • 邻接表
    • 优点:通过链式存储大大节省了存储空间,适合存储稀疏图
    • 不足:边的查找效率低下
  • 十字链表
    • 优点:提高了弧的查找效率,节省了存储空间
    • 不足:十字链表只能存储有向图

相较于邻接矩阵与邻接表,十字链表不仅提高了弧的查找效率,还节省了存储空间。

但是十字链表法并不能像邻接矩阵和邻接表一样不仅可以存储有向图,还可以存储无向图,十字链表法只能够存储有向图。

那对于无向图而言,有没有一种存储结构既能够提高边的查找效率,又能够节省存储空间呢?

二、邻接多重表

在邻接表中,容易求得顶点和边的各种信息,但求两个顶点之间是否存在边儿执行删除边等操作时,需要分别在两个顶点的边表中遍历,效率低。

邻接多重表(Adjacency Multilist​​)是无向图的一种链式存储结构。与十字链表类似,在邻接多重表中,每条边用一个结点表示,其结构如下所示:

ivex
ilink
jvex
jlink
info

其中:

  • ivexjvex中存储的是该边依附的两个顶点编号;
  • ilink 指向的时依附于顶点 i 的下一条边
  • jlink 指向的时依附于顶点 j 的下一条边
  • info 中存放的时该边的相关信息,如边的权值

每个顶点也用一个结点表示,它由两个域组成:

  • data 域存放该顶点的相关信息
  • firstedge 域指向的时依附于该顶点的第一条边

邻接多重表
在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,因为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。

在无向图中,其邻接表与邻接多重表的差别在于——同一条边在两个表中的结点数量不同:

  • 邻接表中,同一条边用两个结点表示
  • 邻接多重表中,只用一个结点表示

三、存储结构

邻接多重表的存储结构中,同样需要定义两种结点类型:

#define MAXSIZE 5
typedef int Edge_ElemType;				// 边信息数据类型
typedef int Vert_ElemType;				// 顶点信息数据类型
typedef struct Edge_Node {int ivex;							// 顶点i的编号int jvex;							// 顶点j的编号struct Edge_Node* ilink, * jlink;	// 依附于顶点i与顶点j的边Edge_ElemType info;					// 边的信息
}ENode;									// 边结点
typedef struct VertexNode {Vert_ElemType data;					// 顶点信息ENode* firstedge;					// 依附于顶点的第一条边的结点
}VNode;									// 顶点结点
typedef struct Adjacency_Multilist​​ {VNode vert_list[MAXSIZE];			// 顶点表int edge_num;						// 边的数量int vert_num;						// 顶点数量
}AMGraph;								// 邻接多重表

当图中的边没有权值时,可以省略边结点中的info域;

四、算法评价

邻接多重表的算法评价同样以邻接多重表的遍历进行评价;

4.1 时间复杂度

在邻接多重表中,遍历所有顶点就是遍历一个顺序表,对于顶点数为 ∣ V ∣ |V| V 的图,其时间复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

遍历所有边只需要将每一条边都遍历一次,对于边数为 ∣ E ∣ |E| E 的图,其时间复杂度为 ∣ E ∣ |E| E

整个图的遍历对应的时间复杂度为 T ( N ) = O ( ∣ V ∣ + ∣ E ∣ ) T(N) = O(|V| + |E|) T(N)=O(V+E)

4.2 空间复杂度

在邻接多重表中,对于顶点数为 ∣ V ∣ |V| V ,边数为 ∣ E ∣ |E| E 的图而言,其需要的空间复杂度为 T ( N ) = O ( ∣ V ∣ + ∣ E ∣ ) T(N) = O(|V| + |E|) T(N)=O(V+E)

五、四种存储方式的总结

下面我们会从五个维度来探讨这四种存储方式:

5.1 空间复杂度

在邻接矩阵中,对于结点数为 n n n 的图,不管是有向图还是无向图都需要申请 n 2 n^2 n2 的空间,因此其空间复杂度均为 n 2 n^2 n2

在邻接表中,对于结点数为 ∣ V ∣ |V| V 和边数为 ∣ E ∣ |E| E 的图,在有向图和无向图中,其空间复杂度并不相同:

  • 有向图中,需要申请 ∣ V ∣ |V| V 个结点空间和 ∣ E ∣ |E| E 个边空间,因此对应的空间复杂度为: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(V+E)
  • 无向图中,需要申请 ∣ V ∣ |V| V 个结点空间和 2 ∗ ∣ E ∣ 2 * |E| 2E 个边空间,因此对应的空间复杂度为: O ( ∣ V ∣ + 2 ∣ E ∣ ) O(|V| + 2|E|) O(V+2∣E)

在十字链表中,对于结点数为 ∣ V ∣ |V| V 和边数为 ∣ E ∣ |E| E 的图,需要申请同等数量的结点空间和边空间,其对应的空间复杂度为: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(V+E)

在邻接多重表中,对于结点数为 ∣ V ∣ |V| V 和边数为 ∣ E ∣ |E| E 的图,需要申请同等数量的结点空间和边空间,其对应的空间复杂度为: O ( ∣ V ∣ + ∣ E ∣ ) O(|V| + |E|) O(V+E)

5.2 找相邻边

在邻接矩阵中,当我们要查找一个顶点的相邻边时,我们只需要遍历该顶点对应的行或者列。在邻接矩阵中,行数与列数都是图的顶点数 ∣ V ∣ |V| V ,因此对应的时间复杂度为 O ( ∣ V ∣ ) O(|V|) O(V)

在邻接表中,当我们要查找一个顶点的相邻边时,对于有向图与无向图而言,其查找邻边的时间复杂度也是有所区别:

  • 无向图中,邻接表查找邻边时,只需要遍历该结点所指向的边表即可
  • 有向图中,邻接表查找邻边时,对于出度与入度的时间复杂度也是有区别:
    • 出度:查找一个顶点的出度,只需要遍历该结点所指向的边表即可
    • 入度:查找一个顶点的入度,需要遍历整个邻接表

在十字链表中,当我们要查找一个顶点的相邻边时,就是查找该顶点的出度与入度,这时只需要分别遍历该结点的出度表与入度表即可

在邻接多重表中,当我们要查找一个顶点的相邻边时,只需要遍历对应的结点所邻接的边表即可

5.3 删除边或结点

在邻接矩阵中:

  • 当我们要删除一条边时,只需要修改对应边在矩阵中的值即可
  • 当我们要删除一个顶点时,需要移动大量的数据

在邻接表中:

  • 有向图:
    • 删除边:只需要删除对应的边结点即可
    • 删除顶点:需要删除与该结点相邻的所有边结点以及该顶点
  • 无向图:
    • 删除边:需要删除其依附的两个顶点所对应的边表中的边结点
    • 删除顶点:需要删除与该结点相邻的所有边结点以及该顶点

在十字链表和邻接多重表中:

  • 删除边:我们只需要删除其对应的边结点即可
  • 删除顶点:需要删除与该顶点相邻的所有边结点以及该顶点信息

不难发现,在图中,当我们要删除一个顶点时,实际上就是需要查找该顶点相邻边并进行删除,因此其删除操作是基于查找操作实现;

5.4 适用于

邻接矩阵由于需要消耗大量的空间用于存储边,因此适用于存储稠密图;

在邻接表中,由于边表是通过链表实现,能够节省存储空间,因此对于稀疏图而言,更加适合用邻接表进行存储;

在十字链表中,只能够存储有向图

在邻接多重表中,只能够存储无向图

5.5 表示方式

邻接矩阵是由各个顶点组成的矩阵,因此,其表示方式是唯一的;

在邻接表、十字链表以及邻接多重表中,由于边表的信息是通过链表进行的存储,因此其边表的表示方式并不唯一;

六、图的基本操作

图的基本操作时独立于图的存储结构的。而对于不同的存储方式,操作算法的具体实现会有着不同的性能。在设计具体算法的实现时,应考虑采用何种存储方式的算法效率会更高。

图的基本操作主要包括:

  • Adjacent(G, x, y): 判断图G是否存在边<x, y>(x, y)
  • Neighbors(G, x): 列出图G中与结点x邻接的边
  • InsertVertex(G, x): 在图G中插入顶点x
  • DeleteVertex(G, x): 在图G中删除顶点x
  • AddEdge(G, x, y): 若无向边(x, y)或有向边<x, y>不存在,则向图G中添加改边
  • RemoveEdge(G, x, y): 若无向边(x, y)或有向边<x, y>存在,则从图G中删除该边
  • FirstNeighbors(G, x): 求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1
  • NextNeighbors(G, x, y): 假设图G中顶点 y 是顶点 x 的一个邻接点,返回除 y 外顶点 x 的下一个邻接点的顶点号,若 y 是 x 的最后一个邻接点,则返回-1
  • Get_edge_value(G, x, y): 获取图G中边(x, y)<x, y> 对应的权值
  • Set_edge_value(G, x, y, v): 设置图G中边(x, y)<x, y> 对应的权值

此外,还有图的遍历算法:按照某种方式访问图中的每个顶点,且仅访问一次。图的遍历算法有两种:

  • 深度优先遍历(Depth-First-Search, DFS)
  • 广度优先遍历(Breadth-First-Search, BFS)

具体内容会在下一个篇章中进行介绍。

结语

邻接多重表以“单边双链”的革新设计,将无向图的存储效率推向新高度——空间占用减半、删边操作跃升至​​O(1)​​,完美解决了邻接表的冗余与低效痛点。

从邻接矩阵的刚性布局到链式结构的动态灵动,每一种存储方案都是空间与时间的精妙权衡,而​​邻接多重表无疑是高频删边场景的无向图终极答案​​。

​​但存储结构只是图算法的起点​​,真正的挑战在于如何基于这些结构实现高效操作。​​下一篇将深入图的广度优先遍历(BFS)​​,解析其在邻接矩阵、邻接表及邻接多重表中的性能差异,并揭秘如何通过存储优化让BFS在千万级节点图中依然游刃有余!

🔍 ​​本文是否为你拨开了图存储的迷雾?​​
👍 ​​点赞​​支持原创深度干货,让技术洞察传播更远!
📁 ​​收藏​​构建你的图论知识库,开发实战随时查阅。
🔄 ​​转发​​至技术社区,与同行探讨存储选型与算法优化。
💬 评论区​​留下你的疑问​​:你在BFS实现中遇到过哪些性能瓶颈?我们共同拆解!
🔔 ​​关注追踪更新​​,《图的广度优先遍历:从理论到超大规模实战》即将上线!

🚀 ​​技术进阶之路,我们并肩前行!​

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

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

相关文章

【Rust】所有权

目录 所有权基本概念所有权介绍栈与堆变量作用域 字符串字符串字面值&#xff08;&str&#xff09;String 类型相互转换所有权 内存结构对比注意事项和常见坑使用场景 内存与分配变量与数据交互的方式&#xff08;一&#xff09;&#xff1a;移动变量与数据交互的方式&…

4月29日日记

终于是考完解析几何了&#xff0c;今天昨天突击了一下&#xff0c;感觉确实学会了很多之前不会的东西&#xff0c;但是可能距离高分还差很多。这次考试不太理想。大部分原因是前期没学&#xff0c;吸取教训&#xff0c;早点开始复习微积分。明天还有一节微积分&#xff0c;但是…

【深度对比】Google Play与IOS 马甲包处理差异分析

在移动应用发布与推广过程中&#xff0c;马甲包&#xff08;Cloned App / Alternate Version&#xff09; 曾被广泛用于流量测试、风险隔离、多品牌运营等场景中。随着 Google Play 与 Apple App Store 审核政策不断收紧&#xff0c;开发者们越来越关注两个平台对“马甲包”的态…

MCP 架构全解析:Host、Client 与 Server 的协同机制

目录 &#x1f3d7;️ MCP 架构全解析&#xff1a;Host、Client 与 Server 的协同机制 &#x1f4cc; 引言 &#x1f9e9; 核心架构组件 1. Host&#xff08;主机&#xff09; 2. Client&#xff08;客户端&#xff09; 3. Server&#xff08;服务器&#xff09; &#…

记录一次无界微前端的简单使用

记录一次无界微前端使用 无界微前端主应用子应用nginx配置 无界微前端 https://wujie-micro.github.io/doc/ 因为使用的是vue项目主应用和次应用都是 所以用的封装的。 https://wujie-micro.github.io/doc/pack/ 主应用 安装 选择对应的版本 # vue2 框架 npm i wujie-vue2…

LLM应用于自动驾驶方向相关论文整理(大模型在自动驾驶方向的相关研究)

1、《HILM-D: Towards High-Resolution Understanding in Multimodal Large Language Models for Autonomous Driving》 2023年9月发表的大模型做自动驾驶的论文&#xff0c;来自香港科技大学和人华为诺亚实验室&#xff08;代码开源&#xff09;。 论文简介&#xff1a; 本文…

FTP-网络文件服务器

部署思路 单纯上传下载ftp系统集成间的共享 samba网络存储服务器 NFS 网络文件服务器&#xff1a;通过网络共享文件或文件夹&#xff0c;实现数据共享 NAS &#xff08; network append storage):共享的是文件夹 FTP&#xff1a;文件服务器samba&#xff1a;不同系统间的文件…

在 Ubuntu 22.04 x64 系统安装/卸载 1Panel 面板

一、 1Panel 是什么&#xff1f; 1Panel 是一款基于 Go 语言开发的现代化开源服务器管理面板&#xff08;类似宝塔面板&#xff09;&#xff0c;专注于容器化&#xff08;Docker&#xff09;和云原生环境管理&#xff0c;提供可视化界面简化服务器运维操作。 1. 1Panel主要功…

Redis | Redis集群模式技术原理介绍

关注&#xff1a;CodingTechWork Redis 集群模式概述 Redis 集群&#xff08;Cluster&#xff09;模式是 Redis 官方提供的分布式解决方案&#xff0c;旨在解决单机 Redis 在数据量和性能上的限制。它通过数据分片、高可用性和自动故障转移等特性&#xff0c;提供了水平扩展和…

Servlet小结

视频链接&#xff1a;黑马servlet视频全套视频教程&#xff0c;快速入门servlet原理servlet实战 什么是Servlet&#xff1f; 菜鸟教程&#xff1a;Java Servlet servlet&#xff1a; server applet Servlet是一个运行在Web服务器&#xff08;如Tomcat、Jetty&#xff09;或应用…

数据库进阶之MySQL 程序

1.目标 1> 了解mysqlId服务端程序 2> 掌握mysql客户端程序的使用 3> 了解工具包中的其他程序 2. MySQL程序简介 本章介绍 MySQL 命令⾏程序以及在运⾏这些程序时指定选项的⼀般语法(如:mysql -uroot -p)。 对常⽤程序进⾏详细的讲解(实用工具的使用方法)&#xf…

VS2022 设置 Qt Project Settings方法

本文解决的问题&#xff1a;创建完成后&#xff0c;如需要用到Sql或者Socket等技术&#xff0c;需要设置Qt Project Settings&#xff1b; 1、打开VS2022编译器&#xff0c;创建QT项目工程 2、创建完成后&#xff0c;点击 解决方案 →右键属性 3、选择 Qt Project Settings →…

React:封装一个评论回复组件

分析 用户想要一个能够显示评论列表&#xff0c;并且允许用户进行回复的组件。可能还需要支持多级回复&#xff0c;也就是对回复进行再回复。然后&#xff0c;我要考虑组件的结构和功能。 首先&#xff0c;数据结构方面&#xff0c;评论应该包含id、内容、作者、时间&#xf…

wx读书某sign算法详解

未加固 版本&#xff1a;9.2.3 前置知识&#xff1a; (v41 & 0xFFFFFFFFFFFFFFFELL) 是一种高效的奇偶检查方法&#xff0c;用于判断数值 v41 是否为奇数。 std::sort<std::lessstd::string,std::string &,std::string>(a1, v6, s); 排序算法 # 完全等价的字…

Django的异步任务队列管理_Celery

1 基本原理 Celery 是一个异步任务队列&#xff0c;能够将耗时操作&#xff08;如发邮件、处理图片、网络爬虫等&#xff09;从 Django 主线程中分离出来&#xff0c;由后台的 worker 处理&#xff0c;避免阻塞请求。Celery 作为独立运行的后台进程&#xff08;Worker&#xf…

【计算机网络】Linux网络的几个常用命令

&#x1f4da; 博主的专栏 &#x1f427; Linux | &#x1f5a5;️ C | &#x1f4ca; 数据结构 | &#x1f4a1;C 算法 | &#x1f152; C 语言 | &#x1f310; 计算机网络 相关文章&#xff1a;计算机网络专栏 目录 ping&#xff08;检测网络连通性&#xff09;…

全开源、私有化部署!轻量级用户行为分析系统-ClkLog

ClkLog是一款支持私有化部署的全开源埋点数据采集与分析系统&#xff0c;兼容Web、App、小程序多端埋点&#xff0c;快速洞察用户访问路径、行为轨迹&#xff0c;并生成多维用户画像。助力中小团队搭建轻量灵活的用户行为分析平台。 为什么需要一款私有化的埋点分析系统&#x…

golang定时器的精度

以 go1.23.3 linux/amd64 为例。 定时器示例代码&#xff1a; package mainimport ("context""fmt""time" )var ctx context.Contextfunc main() {timeout : 600 * time.Secondctx, _ context.WithTimeout(context.Background(), timeout)dea…

svn 远程服务搜索功能

svn服务器没有远程搜索功能&#xff0c;靠人工检索耗时耗力&#xff0c;当服务器文件过多时&#xff0c;全部checkout到本地检索&#xff0c;耗时太久。 1. TortoiseSVN 安装注意事项 下载官网地址&#xff1a;https://tortoisesvn.en.softonic.com/download 安装时选中 co…

uniapp-商城-39-shop 购物车 选好了 进行订单确认4 配送方式2 地址页面

上面讲基本的样式和地址信息&#xff0c;但是如果没有地址就需要添加地址&#xff0c;如果有不同的地址就要选地址。 来看看处理方式&#xff0c; 1、回顾 在delivery-layout中 methods:{goAddress(){uni.navigateTo({url:"/pagesub/pageshop/address/addrlist"})…