【数据结构】邻接矩阵完全指南:原理、实现与稠密图优化技巧​

news2025/7/19 5:29:49

邻接矩阵

  • 导读
  • 一、图的存储结构
    • 1.1 分类
  • 二、邻接矩阵法
    • 2.1 邻接矩阵
    • 2.2 邻接矩阵存储网
  • 三、邻接矩阵的存储结构
  • 四、算法评价
    • 4.1 时间复杂度
    • 4.2 空间复杂度
  • 五、邻接矩阵的特点
    • 5.1 特点1解析
    • 5.2 特点2解析
    • 5.3 特点3解析
    • 5.4 特点4解析
    • 5.5 特点5解析
    • 5.6 特点6解析
  • 结语

邻接矩阵

导读

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

在上一篇中,我们探讨了图的基本概念与术语,如顶点、边、有向图与无向图的区别等。今天,我们将迈入实战阶段,深入解析图的存储结构——这一复杂关系的「翻译器」。

如何将顶点与边的抽象关系转化为代码可操作的数据?面对稀疏图与稠密图,存储结构的选择如何影响算法效率?本文将以邻接矩阵法为起点,系统拆解图的存储逻辑:

  • 从基础定义到代码实现,手把手构建邻接矩阵模型
  • 通过矩阵幂运算揭示「路径数量」的隐藏规律(如 A 2 A^2 A2的奇妙意义)
  • 无向图对称性、顶点度计算等特性的一一验证

无论你是希望夯实基础,还是渴望用数学工具优化算法,本文的图文解析场景化案例都将为你提供清晰的技术地图。

一、图的存储结构

图是由顶点集与边集组成,因此我们想要完整的存储图的信息,那就需要完整的将图中的顶点信息以及边的信息给存储下来。

根据不同图的结构和算法,采用不同的存储方式将对程序的效率产生相当大的影响,因此所选的存储结构应适合于待求解的问题。

1.1 分类

在图中我们将会介绍4种存储结构:

  • 邻接矩阵法——通过一维数组与二维数组实现
  • 邻接表法——通过一维数组与链表实现
  • 十字链表法——通过一维数组与链表实现
  • 邻接多重表——通过一维数组与链表实现

从实现方式的原理上来看,不管是哪种存储结构,都是需要用到两种存储方式,这刚好对应了图中的两种元素——顶点集与边集。

无需多言,相信大家应该已经猜到了,图中对于顶点集的存储,采用的就是一维数组,而对于边集的存储,则会有所区别。

接下来我们就来看一下图的第一种存储结构——邻接矩阵法,它的实现原理究竟是怎么样的。

二、邻接矩阵法

邻接矩阵法就是指用一维数组存储图中的顶点信息,用二维数组存储图中边的信息(各顶点之间的邻接关系)。

存储顶点之间邻接关系的二维数组称为邻接矩阵

对于一个顶点数量为 n n n 的图 G = ( V , E ) G = (V, E) G=(V,E) ,其邻接矩阵 A A A 是一个n阶方阵,即 n × n n × n n×n 的矩阵。

2.1 邻接矩阵

在邻接矩阵中,我们可以用0和1来表示顶点之间的邻接关系:

邻接矩阵

在邻接矩阵中,行坐标和列坐标所代表的结点与一维数组中结点对应的下标一致,矩阵的值就是依附于该顶点的边。

比如上图中的边 ( A , B ) (A, B) (A,B) 对应到矩阵 A A A 中,那就是点 a 01 a_{01} a01 与点 a 10 a_{10} a10 这两个点的值均为1;

如果上图为无向图,且我们要表示弧 < A , B > <A, B> <A,B> ,那么对应的点就是点 a 01 = 1 a_{01} = 1 a01=1 与点 a 10 = 0 a_{10} = 0 a10=0

可以看到,邻接矩阵既可以完整的存储无向图中边的信息,也可以完整的存储有向图中弧的信息;

2.2 邻接矩阵存储网

当我们给图的每条边(弧)加上权值时,该图就变成了一张网,那此时我们又应该如何通过邻接矩阵存储边的信息呢?

对于网的存储也不复杂,我们可以预设一个值如 ∞ \infty 表示两个顶点之间不存在边。

比如当网中各条边的权值都大于等于 0 0 0 时,我们就可以预设 − 1 -1 1 表示两个顶点之间不存在边;

1
2
3
4
a
b
c
d

在这个网中,存储顶点信息的一维数组为:

0123
abcd

当我们用邻接矩阵来存储边的信息时,那对应的邻接矩阵为:

0123
0-1134
11-12-1
232-1-1
34-1-1-1

三、邻接矩阵的存储结构

邻接矩阵的存储结构的C语言表示为:

typedef char VertexType;
typedef int EdgeType;
#define MAXSIZE 5	// 一维数组最大长度
//邻接矩阵法
typedef struct Adjacency_Matrix {
	VertexType Vertex[MAXSIZE];			// 一维数组存储顶点信息
	EdgeType Edge[MAXSIZE][MAXSIZE];	// 二维数组存储边信息
	int len_ver;						// 当前顶点数量
	int len_edge;						// 当前边数量
}AMGraph;								// 邻接矩阵图

经过前面的介绍,相信大家都应该是能够理解这个存储结构的,这里我就不再赘述;

四、算法评价

4.1 时间复杂度

在邻接矩阵中,时间复杂度我们需要从顶点和边两个方面分别来评价:

  • 当我们遍历顶点时,就是在遍历一个一维数组,那么遍历顶点的时间复杂度为: O ( N ) O(N) O(N)
  • 当我们遍历边时,就是在遍历一个二维数组,那么遍历边的时间复杂度为: O ( N 2 ) O(N^2) O(N2)

因此我们遍历整个图的时间复杂度就应该为: T ( n ) = O ( N ) + O ( N 2 ) = O ( N 2 ) T(n) = O(N) + O(N^2) = O(N^2) T(n)=O(N)+O(N2)=O(N2)

4.2 空间复杂度

在邻接矩阵法中,当我们为顶点数为 n n n 的图申请空间时,我们总共需要分别为顶点和边申请空间:

  • 顶点:需要申请 n n n 个空间
  • 边:需要申请 n 2 n^2 n2 个空间

记录顶点数和边数的变量空间为一个常数空间,因此整个图所对应的空间复杂度应该为:
T ( n ) = O ( N ) + O ( N 2 ) + O ( 1 ) = O ( N 2 ) T(n) = O(N) + O(N^2) + O(1) = O(N^2) T(n)=O(N)+O(N2)+O(1)=O(N2)

五、邻接矩阵的特点

图的邻接矩阵表示法具有以下特点:

  1. 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。
  2. 对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非预设值元素如 ∞ \infty )的个数正好是顶点 i i i 的度 T D ( v i ) TD(v_i) TD(vi)
  3. 对于有向图,邻接矩阵的第 i i i 行非零元素(或非 ∞ \infty 元素)的个数正好是顶点 i i i 的出度 O D ( v i ) OD(v_i) OD(vi) ;第 i i i 列非零元素(或非 ∞ \infty 元素)的个数正好是顶点 i i i 的入度 I D ( v i ) ID(v_i) ID(vi)
  4. 用邻接矩阵存图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很大。
  5. 稠密图(边数较多的图)适合采用邻接矩阵的存储表示。
  6. 设图 G G G 的邻接矩阵为 A A A A n A^n An 的元素 A [ i ] [ j ] n A^n_{[i][j]} A[i][j]n 等于由顶点i到顶点j的长度为n的路径的数目。

接下来我们来对这些特点逐个解析;

5.1 特点1解析

在邻接矩阵中,我们采用的是n阶方阵存储的图中边的信息。

在无向图中,当两个顶点之间存在将其连通的边时,从有向图的角度来看,这条边是一条双向边:

a
b
A
B

因此,在邻接矩阵中的反映就一定是一个对称矩阵。

在第三章——特殊矩阵的压缩存储中我们有详细介绍过如何对像对称矩阵这种特殊矩阵进行压缩存储,有需要的朋友可以点击链接详细阅读。

5.2 特点2解析

在无向图中,一个顶点的度就是依附于该顶点的边的数量。

在邻接矩阵中,每一行或者每一列都表示的是依附于该顶点的边。

因此在第 i i i 行或者第 i i i 列中所有不为0,或者存储网时,不为预设值(如 ∞ 、 − 1 … … \infty、-1…… 1……)的点 a i j a_{ij} aij 或者 a j i a_{ji} aji 的数量之和就是该顶点 i i i 的度 T D ( v i ) TD(v_i) TD(vi)

这里需要注意,当我们计算了第 i i i 行就不需要再计算第 i i i 列,即行和列只需要取其一即可;

5.3 特点3解析

在有向图中,邻接矩阵中点 a i j a_{ij} aij 的行坐标 i i i 表示的是弧尾,列坐标 j j j 表示的是弧头。这里我们用实例来说明:

a
b

在这个有向图中,我们在存储顶点 a , b a, b a,b 时分别将顶点 a a a 存储在下标0,顶点 b b b 存储在下标1处,此时弧 < a , b > <a, b> <a,b> 在邻接矩阵中存储时,代表该弧的点为 a 01 a_{01} a01 ,可以看到该点的横坐标就是弧尾,纵坐标就是弧头;

b
a

同理,弧 < b , a > <b, a> <b,a> 所对应的邻接矩阵中的点为 a 10 a_{10} a10,同样的,横坐标代表的是弧尾,纵坐标代表的是弧头。

因此我们说在有向图中,邻接矩阵的第 i i i 行非零元素的个数正好是顶点 i i i 的出度 O D ( v i ) OD(v_i) OD(vi);
i i i 列非零元素的个数正好是顶点 i i i 的入度 I D ( v i ) ID(v_i) ID(vi)

5.4 特点4解析

在邻接矩阵中,我们要求边的个数,实际上就是遍历整个二维数组,而二维数组遍历的时间复杂度为: O ( N 2 ) O(N^2) O(N2) ,因此所耗费的时间代价是巨大的。

5.5 特点5解析

在邻接矩阵中,我们在存储边的信息时,不管两个之间是否存在边,我们都为其申请了空间。

试想一下,如果在一个稀疏图中,边的数量为 ∣ E ∣ < ∣ V ∣ l o g 2 ∣ V ∣ |E| < |V|log_2|V| E<Vlog2V ,也就是说,如果该图中有4个顶点时,边的数量不足8个,但是我们通过邻接矩阵存储时,申请了 4 2 = 16 4^2 = 16 42=16 个空间。

此时我们对空间的实际利用率 < 50 < 50% <50 ,换句话说就是我们浪费的空间 > 50 > 50% >50

这么一看,当我们用邻接矩阵法存储这种边数量很少的图时,会造成大量的空间浪费。

因此,邻接矩阵法不适合存储稀疏图这种边数量很少的图,更加适合存储稠密图这种边数量很多的图。

5.6 特点6解析

要理解特点6,首先我们要清楚矩阵相乘的规则:

  • 当且仅当一个矩阵的行数与另一个矩阵的列数相等时两个矩阵才能相乘;
  • 当矩阵 A A A m × n m × n m×n 的矩阵与矩阵 B B B n × m n × m n×m 的矩阵相乘时, 得到的矩阵 C C C 中的元素 c i j c_{ij} cij 为:
    c i j = ∑ k = 1 n a i k ⋅ b k j c_{ij} = \sum_{k=1}^{n} a_{ik} \cdot b_{kj} cij=k=1naikbkj

这里我们以有向图 G G G 为例进行说明:

a
b

在该有向图中,顶点a对应的下标为0,顶点b对应的下标为1,其邻接矩阵 A A A 如下所示:

01
001
100

对应的 A 2 A^2 A2 中的个元素为:
a 00 ′ = a 00 × a 00 + a 01 × a 10 = 0 + 0 = 0 a 01 ′ = a 00 × a 01 + a 01 × a 11 = 0 + 0 = 0 a 10 ′ = a 10 × a 00 + a 11 × a 10 = 0 + 0 = 0 a 11 ′ = a 10 × a 01 + a 11 × a 11 = 0 + 0 = 0 a'_{00} = a_{00} × a_{00} + a_{01} × a_{10} = 0 + 0 = 0\\ a'_{01} = a_{00} × a_{01} + a_{01} × a_{11} = 0 + 0 = 0 \\ a'_{10} = a_{10} × a_{00} + a_{11} × a_{10} = 0 + 0 = 0\\ a'_{11} = a_{10} × a_{01} + a_{11} × a_{11} = 0 + 0 = 0 a00=a00×a00+a01×a10=0+0=0a01=a00×a01+a01×a11=0+0=0a10=a10×a00+a11×a10=0+0=0a11=a10×a01+a11×a11=0+0=0

a 00 ′ a'_{00} a00 为例,该点表示的是顶点 a ′ a' a 到顶点 a ′ a' a 的长度为2的路径的数目。

a 00 ′ = a 00 × a 00 + a 01 × a 10 = 0 + 0 = 0 a'_{00} = a_{00} × a_{00} + a_{01} × a_{10} = 0 + 0 = 0 a00=a00×a00+a01×a10=0+0=0

该公式中各项的含义为:

  • a 00 a_{00} a00:顶点a到顶点a的弧
  • a 01 a_{01} a01:顶点a到顶点b的弧
  • a 10 a_{10} a10:顶点b到顶点a的弧

我们要想得到 A 2 A^2 A2 中 顶点 a ′ a' a 到顶点 a ′ a' a 的长度为2的路径,那我们就有两种方式从顶点 a ′ a' a 到达顶点 a ′ a' a

  • 从顶点 a ′ a' a 到达顶点 a ′ a' a,再从顶点 a ′ a' a 到达顶点 a ′ a' a,此时路径长度为2;
  • 从从顶点 a ′ a' a 到达顶点 b ′ b' b,再从顶点 b ′ b' b 到达顶点 a ′ a' a,此时的路径长度为2;

因此要得到长度为2的路径,就必须存在弧 < b , a > <b, a> <b,a> ,但是图中不存在弧 < b , a > <b, a> <b,a> 因此就不存在长度为2的路径,此时长度为2的路径的数量为0;

同理,我们也能够验证矩阵 A 2 A^2 A2 中的其它三个元素。

这个特点比较绕,如果实在不理解也没关系,我们只做了解即可。

结语

邻接矩阵法以矩阵的简洁性,将图的顶点与边关系凝练为二维数组的0/1或权值,成为稠密图存储的经典选择。通过本文的解析,我们可总结其核心价值:

  1. 直观性:矩阵行列直接对应顶点,快速判断任意两顶点是否邻接(O(1)时间复杂度);
  2. 数学优势:矩阵运算(如幂运算)可高效推导路径数与连通性;
  3. 场景适配:尤其适合边数接近顶点数平方的稠密图,避免空间浪费。

然而,邻接矩阵的O(n²)空间复杂度也提醒我们:面对稀疏图(如社交网络),邻接表等结构可能更具优势。技术的选择永远服务于具体问题,理解不同存储结构的特性,方能灵活应对千变万化的算法需求。

拓展思考:若图的顶点动态增减,邻接矩阵如何优化?权值无穷大(∞)在代码中应如何合理表示?欢迎在评论区探讨你的见解,或继续阅读本系列的下一篇——《邻接表:稀疏图的存储利器》。


🌟 如果本文对你有所帮助,欢迎:
关注[👉] 获取更多算法与数据结构深度解析
点赞[👍] 支持原创内容
收藏[📁] 备查或分享给需要的伙伴
转发[🔄] 让知识传播更远

你的每一次互动,都是我们持续创作的动力!

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

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

相关文章

【嵌入式-stm32电位器控制以及旋转编码器控制LED亮暗】

嵌入式-stm32电位器控制LED亮暗 任务1代码1Key.cKey.hTimer.cTimer.hPWM.cPWM.hmain.c 实验现象1任务2代码2Key.cKey.hmain.c 实验现象2问题与解决总结 源码框架取自江协科技&#xff0c;在此基础上做扩展开发。 任务1 本文主要介绍利用stm32f103C8T6实现电位器控制PWM的占空比…

Uniapp 集成极光推送(JPush)完整指南

文章目录 前言一、准备工作1. 注册极光开发者账号2. 创建应用3. Uniapp项目准备 二、集成极光推送插件方法一&#xff1a;使用UniPush&#xff08;推荐&#xff09;方法二&#xff1a;手动集成极光推送SDK 三、配置原生平台参数四、核心功能实现1. 获取RegistrationID2. 设置别…

2025年常见渗透测试面试题-sql(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 SQLi 一、发现test.jsp?cid150 注入点的5种WebShell获取思路 1. 文件写入攻击 2. 日志文件劫持 3.…

【RabbitMQ】队列模型

1.概述 RabbitMQ作为消息队列&#xff0c;有6种队列模型&#xff0c;分别在不同的场景进行使用&#xff0c;分别是Hello World&#xff0c;Work queues&#xff0c;Publish/Subscribe&#xff0c;Routing&#xff0c;Topics&#xff0c;RPC。 下面就分别对几个模型进行讲述。…

StarRocks 助力首汽约车精细化运营

作者&#xff1a;任智红&#xff0c;首汽约车大数据负责人 更多交流&#xff0c;联系我们&#xff1a;https://wx.focussend.com/weComLink/mobileQrCodeLink/334%201%202/ffbe5 导读&#xff1a; 本文整理自首汽约车大数据负责人任智红在 StarRocks 年度峰会上的演讲&#xf…

痉挛性斜颈康复助力:饮食调养指南

痉挛性斜颈患者除了积极治疗&#xff0c;合理饮食也能辅助缓解症状&#xff0c;提升生活质量。其健康饮食可从以下方面着手&#xff1a; 高蛋白质食物助力肌肉修复 痉挛性斜颈会导致颈部肌肉异常收缩&#xff0c;消耗较多能量&#xff0c;蛋白质有助于肌肉的修复与维持。日常可…

mysql镜像创建docker容器,及其可能遇到的问题

前提&#xff0c;已经弄好基本的docker服务了。 一、基本流程 1、目录准备 我自己的资料喜欢放在 /data 目录下&#xff0c;所以老规矩&#xff1a; 先进入 /data 目录&#xff1a; cd /data 创建 mysql 目录并进入&#xff1a; mkdir mysql cd mysql 2、镜像查找 docke…

JavaEE——线程的状态

目录 前言1. NEW2. TERMINATED3. RUNNABLE4. 三种阻塞状态总结 前言 本篇文章来讲解线程的几种状态。在Java中&#xff0c;线程的状态是一个枚举类型&#xff0c;Thread.State。其中一共分为了六个状态。分别为&#xff1a;NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING, TERMI…

RuntimeError: Error(s) in loading state_dict for ChartParser

一 bug错误 最近使用千问大模型有一个bug&#xff0c;报错信息如下 raise RuntimeError(Error(s) in loading state_dict for {}:\n\t{}.format( RuntimeError: Error(s) in loading state_dict for ChartParser:Unexpected key(s) in state_dict: "pretrained_model.em…

2025 年安徽交安安全员考试:利用记忆宫殿强化记忆​

安徽考生在面对交安安全员考试繁杂的知识点时&#xff0c;记忆宫殿是强大的记忆工具。选择一个熟悉且空间结构清晰的场所作为记忆宫殿&#xff0c;如自己居住的房屋。将房屋的不同区域&#xff0c;如客厅、卧室、厨房等&#xff0c;分别对应不同知识板块&#xff0c;像客厅对应…

安全编码课程 实验6 整数安全

实验项目 实现安全计数器&#xff1a;实现 Counter 结构&#xff0c;确保计数范围为 0~100。 实验要求&#xff1a; 1、使用 struct 封装计数值value&#xff1b; 2、计数器初值为 0&#xff1b; 3、increment() 方法增加计数&#xff0c;但不能超过 100&#xff1b; 4、decrem…

解决上传PDF、视频、音频等格式文件到FTP站点时报错“将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上”问题

一、问题描述 可以将文本文件(.txt格式),图像文件(.jpg、.png等格式)上传到我们的FTP服务器上;但是上传一些PDF文件、视频等文件时就会报错“ 将文件复制到FTP服务器时发生错误。请检查是否有权限将文件放到该服务器上。 详细信息: 200 Type set to l. 227 Entering Pas…

【Linux操作系统】:信号

Linux操作系统下的信号 一、引言 首先我们可以简单理解一下信号的概念&#xff0c;信号&#xff0c;顾名思义&#xff0c;就是我们操作系统发送给进程的消息。举个简单的例子&#xff0c;我们在写C/C程序的时候&#xff0c;当执行a / 0类似的操作的时候&#xff0c;程序直接就挂…

经典频域分析法(Bode图、Nyquist判据) —— 理论、案例与交互式 GUI 实现

目录 经典频域分析法(Bode图、Nyquist判据) —— 理论、案例与交互式 GUI 实现一、引言二、经典频域分析方法的基本原理2.1 Bode 图分析2.2 Nyquist 判据三、数学建模与公式推导3.1 一阶系统的频域响应3.2 多极系统的 Bode 图绘制3.3 Nyquist 判据的数学描述四、经典频域分析…

使用scoop一键下载jdk和实现版本切换

安装 在 PowerShell 中输入下面内容&#xff0c;保证允许本地脚本的执行&#xff1a; set-executionpolicy remotesigned -scope currentuser然后执行下面的命令安装 Scoop&#xff1a; iwr -useb get.scoop.sh | iex国内用户可以使用镜像源安装&#xff1a;powershell iwr -us…

对状态模式的理解

对状态模式的理解 一、场景二、不采用状态模式1、代码2、缺点 三、采用状态模式1、代码1.1 状态类1.2 上下文&#xff08;这里指&#xff1a;媒体播放器&#xff09;1.3 客户端 2、优点 一、场景 同一个东西&#xff08;例如&#xff1a;媒体播放器&#xff09;&#xff0c;有一…

vue2(webpack)集成electron和 electron 打包

前言 之前发过一篇vue集成electron的文章&#xff0c;但是用vue3vite实现的&#xff0c;在vue2webpack工程可能不适用&#xff0c;所以这篇文章就主要介绍vue2webpack集成electron方法 创建项目 vue create vue-electron-demo目录架构 vue-electron-demo/ ├── src/ …

C++内存管理优化实战:提升应用性能与效率

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开发技术&#xff0c;能熟练应用常用数据库SQL server,Oracle…

redis数据迁移之通过redis-dump镜像

这里写目录标题 一、redis-dump 镜像打包1.1 安装windows docker1.2 idea项目创建1.3 idea镜像打包 二、redis数据迁移2.1 数据导出2.2 数据导入 一、redis-dump 镜像打包 没有找到可用的redis-dump镜像&#xff0c;需要自己打包一下&#xff0c;这里我是在idea直接打包的 1.…

redis导入成功,缺不显示数据

SpringBootTest class SecurityApplicationTests {AutowiredStringRedisTemplate template; //添加这句代码&#xff0c;自动装载&#xff0c;即可解决文章三处代码报错Testvoid contextLoads() {String compact Jwts.builder().signWith(Jwts.SIG.HS512.key().build()).subj…