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

邻接矩阵

  • 导读
  • 一、图的存储结构
    • 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.mzph.cn/news/900028.shtml

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

相关文章

Docker Registry 清理镜像最佳实践

文章目录 registry-clean1. 简介2. 功能3. 安装 docker4. 配置 docker5. 配置域名解析6. 部署 registry7. Registry API 管理8. 批量清理镜像9. 其他10. 参考registry-clean 1. 简介 registry-clean 是一个强大而高效的解决方案,旨在简化您的 Docker 镜像仓库管理。通过 reg…

UART双向通信实现(序列机)

前言 UART&#xff08;通用异步收发传输器&#xff09;是一种串行通信协议&#xff0c;用于在电子设备之间进行数据传输。RS232是UART协议的一种常见实现标准&#xff0c;广泛应用于计算机和外围设备之间的通信。它定义了串行数据的传输格式和电气特性&#xff0c;以确…

机器学习算法分类全景解析:从理论到工业实践(2025新版)

一、机器学习核心定义与分类框架 1.1 机器学习核心范式 机器学习本质是通过经验E在特定任务T上提升性能P的算法系统&#xff08;Mitchell定义&#xff09;。其核心能力体现在&#xff1a; 数据驱动决策&#xff1a;通过数据自动发现模式&#xff0c;而非显式编程&#xff08…

perf‌命令详解

‌perf 命令详解‌ perf 是 Linux 系统中最强大的 ‌性能分析工具‌&#xff0c;基于内核的 perf_events 子系统实现&#xff0c;支持硬件性能计数器&#xff08;PMC&#xff09;、软件事件跟踪等功能&#xff0c;用于定位 CPU、内存、I/O 等性能瓶颈。以下是其核心用法与实战…

【大模型基础_毛玉仁】6.4 生成增强

目录 6.4 生成增强6.4.1 何时增强1&#xff09;外部观测法2&#xff09;内部观测法 6.4.2 何处增强6.4.3 多次增强6.4.4 降本增效1&#xff09;去除冗余文本2&#xff09;复用计算结果 6.4 生成增强 检索器得到相关信息后&#xff0c;将其传递给大语言模型以期增强模型的生成能…

Leetcode 合集 -- 排列问题 | 递归

题目1 子集2 思路 代码 题目2 全排列2 思路 代码 题目3 排列总和 思路 代码 题目4 排列总和2 思路 代码

vue-office 支持预览多种文件(docx、excel、pdf、pptx)预览的vue组件库

官网地址&#xff1a;https://github.com/501351981/vue-office 支持多种文件(docx、excel、pdf、pptx)预览的vue组件库&#xff0c;支持vue2/3。也支持非Vue框架的预览。 1.在线预览word文件&#xff08;以及本地上传预览&#xff09; 1.1&#xff1a;下载组件库 npm inst…

【trino】trino配置证书https tls/ssl访问

trini版本470 一、官方文档 doc 在Security/TLS and HTTPS、Security/PEM files和Security/JKS files下 openssl文档 二、配置trino 2.1 创建server.cnf文件 [ req ] distinguished_name req_distinguished_name req_extensions v3_req[ req_distinguished_name ] coun…

ZCC8702,LED驱动芯片的“六边形战士”可替代SY8707

在LED照明的璀璨舞台上&#xff0c;驱动芯片犹如幕后英雄&#xff0c;默默掌控着灯光的闪耀与变幻。ZCC8702作为一款集大成的LED驱动芯片&#xff0c;凭借其卓越的性能、广泛的应用范围和出色的稳定性&#xff0c;成为了这个领域中当之无愧的“六边形战士”。今天&#xff0c;就…

Vue 数据传递流程图指南

今天&#xff0c;我们探讨一下 Vue 中的组件传值问题。这不仅是我们在日常开发中经常遇到的核心问题&#xff0c;也是面试过程中经常被问到的重要知识点。无论你是初学者还是有一定经验的开发者&#xff0c;掌握这些传值方式都将帮助你更高效地构建和维护 Vue 应用 目录 1. 父…

Git Restore 命令详解与实用示例

文章目录 Git Restore 命令详解与实用示例1. 恢复工作区文件到最后一次提交的状态基本命令示例恢复所有更改 2. 恢复某个文件到特定提交的状态基本命令示例 3. 恢复暂存区的文件基本命令示例恢复所有暂存的文件 git restore 的常见选项git restore 与 git checkout 比较总结 Gi…

AI 防口误指南_LLM 输出安全实践

在数字化转型的浪潮中&#xff0c;大语言模型(以下统称LLM)已成为企业技术栈中不可或缺的智能组件&#xff0c;这种强大的AI技术同时也带来了前所未有的安全挑战。它输出的内容如同双面刃&#xff0c;一面闪耀着效率与创新的光芒&#xff0c;另一面却隐藏着"幻觉"与不…

程序化广告行业(55/89):DMP与DSP对接及数据统计原理剖析

程序化广告行业&#xff08;55/89&#xff09;&#xff1a;DMP与DSP对接及数据统计原理剖析 大家好呀&#xff01;在数字化营销的大趋势下&#xff0c;程序化广告已经成为众多企业实现精准营销的关键手段。上一篇博客我们一起学习了程序化广告中的人群标签和Look Alike原理等知…

运维之 Centos7 防火墙(CentOS 7 Firewall for Operations and Maintenance)

运维之 Centos7 防火墙 1.介绍 Linux CentOS 7 防火墙/端口设置&#xff1a; 基础概念&#xff1a; 防火墙是一种网络安全设备&#xff0c;用于监控和控制网络流量&#xff0c;以保护计算机系统免受未经授权的访问和恶意攻击。Linux CentOS 7操作系统自带了一个名为iptables的…

第十五届蓝桥杯大赛软件赛省赛Python 大学 C 组题目试做(下)【本期题目:砍柴,回文字符串】

okk&#xff0c;大伙&#xff0c;这一期我们就把C组的题目刷完。 本期题目&#xff1a;砍柴&#xff0c;回文字符串 文章目录 砍柴题目思路分析举个栗子思路总结 代码 回文字符串题目思路分析代码 感谢大伙观看&#xff0c;别忘了三连支持一下大家也可以关注一下我的其它专栏&a…

Design Compiler:库特征分析(ALIB)

相关阅读 Design Compilerhttps://blog.csdn.net/weixin_45791458/category_12738116.html?spm1001.2014.3001.5482 简介 在使用Design Compiler时&#xff0c;可以对目标逻辑库进行特征分析&#xff0c;并创建一个称为ALIB的伪库&#xff08;可以被认为是缓存&#xff09;&…

MySQL索引原理:从B+树手绘到EXPLAIN

最近在学后端&#xff0c;学到了这里做个记录 一、为什么索引像书的目录&#xff1f; 类比&#xff1a;500页的技术书籍 vs 10页的目录缺点&#xff1a;全表扫描就像逐页翻找内容优点&#xff1a;索引将查询速度从O(n)提升到O(log n) 二、B树手绘课堂 1. 结构解剖&#xff0…

全连接RNN反向传播梯度计算

全连接RNN反向传播梯度计算 RNN数学表达式BPTT(随时间的反向传播算法)参数关系网络图L对V的梯度L对U的梯度L对W和b的梯度 RNN数学表达式 BPTT(随时间的反向传播算法) 参数关系网络图 L对V的梯度 L对U的梯度 L对W和b的梯度

C++高效读取大规模文本格式点云(windows)

需使用VS2017及以上版本&#xff0c;C语言标准选择C17&#xff0c;支持OpenMP。 执行效率明显优于ifstream stof。 // 点云数据结构 struct PointXYZ {std::array<float, 3> coord; };float string_to_float_fast(const std::string& str) {float value;auto [p…

【Linux】进程信号的捕捉处理

个人主页~ 进程信号的捕捉处理 一、信号捕捉处理的概述1、信号捕捉处理全过程2、用户态和内核态的区别&#xff08;一&#xff09;用户态&#xff08;二&#xff09;内核态&#xff08;三&#xff09;用户态与内核态的切换&#xff08;四&#xff09;硬件条件 二、再谈进程地址…