稀疏数组、广义表与矩阵压缩存储

在线性结构和字符串这些基础内容之后,数据结构学习很快就会碰到一个现实问题:并不是所有数据都适合“老老实实一个位置存一个值”这种最直白的方式。很多时候,原始数据虽然看起来规模很大,但真正有效的信息其实很少。如果还按完整数组或完整矩阵存,就会造成大量空间浪费。

这也是为什么教材里会专门引出稀疏数组、广义表和矩阵压缩存储。它们背后的共同思想其实很统一:

当数据分布具有明显结构特征时,可以利用这些特征改变存储方式,用更少的空间表示同样的信息。

所以这一篇的重点,不是把三个概念孤零零地背下来,而是看清它们都在解决同一类问题:如何在“逻辑结构不变”的前提下,用更经济的方式组织复杂数据。

一、为什么需要压缩存储

最朴素的存储方式通常是“位置全展开”。例如:

  • 一维数组按下标完整存;
  • 二维矩阵按行列完整展开;
  • 嵌套结构逐层按普通节点展开。

这种方式的优点是简单、访问直接,但缺点也很明显:

  • 如果大量位置都是默认值,就会浪费空间;
  • 如果结构本身是嵌套不规则的,普通线性存储不够灵活;
  • 如果只关心非零或非空部分,完整展开会让有效信息被大量“空白”稀释。

所以压缩存储的本质是:

  • 不存无意义的重复信息;
  • 或者把结构特征编码进存储方式里;
  • 让空间使用更贴近真实有效数据量。

二、什么是稀疏数组

稀疏数组通常是指:

在一个规模较大的数组或矩阵中,绝大多数元素都为 0 或默认值,真正非零的元素非常少。

例如一个 1000 × 1000 的棋盘,如果只有十几个位置上有棋子,那么完整存储 100 万个格子显然非常浪费。

这时就可以把重点转移到“记录非零元素”上,而不是把所有空位都存下来。

三、稀疏数组最常见的表示思路

1. 三元组表示法

这是最经典的方式。

对于每一个非零元素,只记录三项:

  • 行号;
  • 列号;
  • 元素值。

于是原本巨大的矩阵,就可以被压缩成一张“非零元素清单”。

这种方式的优点是:

  • 空间只和非零元素个数有关;
  • 结构直观;
  • 很适合稀疏程度很高的矩阵。
2. 行逻辑更强的变体

有时还会额外按行组织这些非零元素,方便按行访问或转置操作。

也就是说,三元组只是基础形式,围绕它还能发展出更适合不同操作需求的结构。

四、稀疏数组适合哪些场景

典型场景包括:

  • 棋盘和地图表示;
  • 图的邻接关系表示;
  • 科学计算中的大型稀疏矩阵;
  • 推荐系统和特征工程中的稀疏特征向量;
  • 搜索和机器学习中的高维稀疏表示。

这些问题的共同点都是:

  • 数据维度大;
  • 有效项少;
  • 直接完整存储不划算。

五、什么是广义表

广义表的概念相对更抽象一些。它可以理解成:

表中的元素既可以是原子,也可以是另一个表。

例如:

1
(a, b, (c, d), (e, (f, g)))

这里:

  • ab 可以看成原子;
  • (c, d) 本身又是一个子表;
  • (e, (f, g)) 则是更深一层的嵌套表。

所以广义表的核心不在于“元素排成一列”,而在于它允许层级和嵌套结构出现。

六、广义表为什么重要

因为它提供了一种更灵活的结构表达方式。

普通线性表默认所有元素处于同一层;但现实数据中,经常会出现:

  • 目录树;
  • 表达式树;
  • 嵌套配置;
  • JSON / Lisp 这类层级数据;
  • 编译器中的语法结构。

广义表虽然在现代工程里未必以这个名字高频出现,但它背后的思想非常重要:

一个数据项本身可以继续包含结构,而不是只包含简单值。

从这个角度看,广义表其实是理解递归结构和树形结构的一块桥梁。

七、广义表的常见特征

1. 可以递归定义

广义表非常适合用递归方式描述,因为子表本身仍然是表。

2. 长度和深度是两个不同概念
  • 长度通常指最外层元素个数;
  • 深度则反映嵌套层数。
3. 存储实现往往更依赖链式结构

因为嵌套层次不规则,用纯顺序存储不够自然。

这也是为什么广义表在概念上很适合帮助理解“结构中的结构”。

八、什么是矩阵压缩存储

矩阵压缩存储可以看成是把“稀疏思想”进一步细化后的结果。

因为除了“非零元素少”这种稀疏情形,还有一些矩阵虽然不是普通意义上的稀疏矩阵,但它本身具有特殊结构。例如:

  • 对称矩阵;
  • 上三角矩阵;
  • 下三角矩阵;
  • 对角矩阵;
  • 带状矩阵。

这些矩阵里,有很多位置的值并不是随机分布,而是天然可以由结构规律推导出来。

此时如果仍按普通二维数组完整存储,就会重复保存大量冗余信息。

九、对称矩阵为什么适合压缩

以对称矩阵为例,满足:

1
a[i][j] = a[j][i]

这意味着:

  • 主对角线两侧的信息是镜像的;
  • 只保存上三角或下三角的一半数据就够了。

于是我们就可以:

  • 按行或按列把一半元素线性展开;
  • 通过下标映射公式恢复原矩阵位置。

本质上,这是一种用“结构规律”来减少空间占用的做法。

十、三角矩阵、对角矩阵和带状矩阵的思路

它们的共同点都在于:

  • 只有某些区域有意义;
  • 其他区域固定为 0 或固定值;
  • 可以只存有效区域。

例如:

  • 上三角矩阵只保存主对角线及其上方;
  • 对角矩阵只保存对角线元素;
  • 带状矩阵只保存主对角线附近的若干条带。

所以矩阵压缩存储的核心思想并不复杂:

不必忠实存下每一个逻辑位置,而是只存真正有信息量的部分。

十一、压缩存储的代价是什么

节省空间从来不是没有代价的。

1. 访问逻辑更复杂

普通二维数组可以直接 a[i][j] 访问,但压缩后通常需要先做下标映射或查找定位。

2. 更新操作可能更麻烦

如果数据结构经常变化,压缩结构的维护成本可能会上升。

3. 并不适合所有场景

只有在稀疏程度高、结构规律明显,或者空间特别敏感时,压缩存储才真正划算。

这说明数据结构设计永远是权衡:

  • 节省空间;
  • 增加访问复杂度;
  • 根据场景决定是否值得。

十二、这些内容和后面树、图有什么联系

这篇虽然看起来像是“几个离散的小知识点”,但它们其实在为后面很多内容做铺垫。

例如:

  • 广义表帮助理解递归嵌套结构,会自然过渡到树;
  • 稀疏矩阵和压缩表示,会帮助理解图的邻接矩阵与稀疏图存储;
  • 结构规律决定存储方式,这种思想会在后面的 B+ 树、Trie、位图、布隆过滤器中不断出现。

所以这一篇真正要学到的,不只是几个定义,而是一种更一般的存储意识:

数据怎么分布,决定了结构怎么设计。

十三、学习这部分时最容易踩的坑

1. 只记定义,不理解共同思想

这三个主题看似不同,但它们都在做“利用结构特征节省存储”的事。

2. 以为压缩存储一定更好

空间更省不代表整体更优,访问复杂度和实现复杂度也要考虑。

3. 不区分“逻辑结构”和“存储结构”

压缩后逻辑上还是那个矩阵或表,只是底层表示不同。

4. 忽略映射关系

很多压缩结构真正的难点不是“知道能压缩”,而是“知道如何从逻辑位置映射到物理位置”。

总结

稀疏数组、广义表和矩阵压缩存储的重要性,不只是因为它们出现在教材里,而是因为它们共同说明了一件很重要的事:数据结构设计不能只看逻辑形式,还必须看数据分布特征。真正值得先建立起来的,是这些核心认识:

  • 当数据规模大但有效信息少时,完整展开存储会造成明显浪费;
  • 稀疏数组通过只记录非零元素来压缩空间;
  • 广义表通过允许元素继续包含子表,表达更复杂的层级结构;
  • 特殊矩阵可以利用对称性、三角性和带状结构做压缩存储;
  • 压缩存储的本质,是用更复杂的定位逻辑换取更高的空间利用率;
  • 真正学会这一篇,不是会背几个名词,而是知道什么时候应该利用数据分布特征重新设计存储方式。

把这一篇理解透之后,后面再去学树、图和各种高级结构时,你会更容易意识到:很多优秀的数据结构,真正厉害的地方不只是“存得下”,而是“存得更有针对性”。

参考资源