Appearance
特殊矩阵的压缩存储
2026 大纲 三(五)特殊矩阵的压缩存储(地址公式的地基见《多维数组的存储》)。
四类矩阵、八个公式,推法只有一条
一个
公式看着有八个,推法其实只有一条:前
| 矩阵 | 存哪一半 / 哪些位置 | 压缩后大小 | 关键公式(矩阵 1-based,数组 0-based) |
|---|---|---|---|
| 对称矩阵(上三角,行优先) | |||
| 对称矩阵(上三角,列优先) | |||
| 下三角(行优先) | |||
| 三角矩阵 | 一半三角 | 同对应三角;常数区全部映射到末位 | |
| 三对角矩阵 | |||
| 稀疏矩阵(三元组) | 任意稀疏、无规律 | 显式存 | |
| 稀疏矩阵(十字链表) | 任意稀疏 | 链表结点,无下标公式 |
| 🔴 查任一元素的三步 | ① 判断它在不在被存的那个三角;② 不在就先按对称性交换下标;③ 再代公式。跳过前两步直接代,是这一节最容易犯的错 |
| 🔴 对称 vs 三角的唯一实质差别 | 对称:另一半内容重复,查它要交换下标;三角:另一半全是常数 |
| 🔴 三对角公式对边界行也成立 | 第 1 行的"前缀少 1、行内多 1"正好抵消,不必单独处理 |
| 前三类 vs 稀疏矩阵 | 前三类位置能由 |
| 压缩的目标 | 只保留有用信息,同时仍能在 |
| 🔴 压缩不是无条件更优 | 三元组要 |
| 🔴 邻接矩阵不是压缩结构 | 它是图的稠密存储, |
查任一元素,固定三步:① 判断它在不在被存的那个三角;② 不在就先按对称性交换下标;③ 再代公式。跳过前两步直接代公式,是这一节最容易犯的错。
第 ② 步怎么处理,取决于矩阵的类型——对称矩阵与三角矩阵的唯一实质差别就在这里:
- 对称矩阵:另一半的内容是重复的,所以查它要交换下标再代同一个公式;
- 三角矩阵:另一半全是常数
,所以查它直接返回末位那一格,根本不用算。
判据是题面写的是"对称",还是"其余元素均为常数 / 均为 0"。
全文口径:矩阵元素下标从 1 开始(
),压缩数组下标从 0 开始(C 语言习惯)。 题面若是其他组合,先按本口径算出结果,最后一步再 转换,不要临场改公式—— 临场改公式是这一节的主要出错来源。两个 base 互相独立,不要混成一个。
交互可视化
顶部切换「对称 / 三角 / 三对角 / 稀疏」,点击网格任意元素查看下标推导; 也可直接输入
对称矩阵:两个公式的完整推导与手算验证(想会推不想背就展开)
上三角 · 行优先。 第
text
i=1 : m11 m12 m13 ... m1n (n 个)
i=2 : m22 m23 ... m2n (n-1 个)
i=3 : m33 ... m3n (n-2 个)
...
i=n : mnn (1 个)前
行的元素总数: 。 这是首项 、末项 、共 项的等差数列,和为 第
行内 之前:从 数到 ,共 个。
一次演算:
手算验证:前 5 行共
上三角 · 列优先。 第
text
j=1 : m11 (1 个)
j=2 : m12 m22 (2 个)
j=3 : m13 m23 m33 (3 个)
...
j=n : m1n m2n m3n ... mnn (n 个)- 前
列的元素总数: ; - 第
列内 之前:从 数到 ,共 个。
一次演算:
手算验证:前 6 列共
漏掉第一步(下标交换)是这类题的典型错法。自查动作:代入公式前, 先把
和 比一次大小,确认它满足被存三角的条件。
行优先与列优先的对照(题面会明确写出用哪种):
| 行优先(存上三角) | 列优先(存上三角) | |
|---|---|---|
| 第 | ||
| 前 | ||
| 行内/列内偏移 |
不必机械记忆两套公式——理解"前
三角矩阵:公式、常数位,以及与对称矩阵的并排对照(想彻底分清两者就展开)
上三角矩阵:所有
text
* * * *
0 * * * ← 下三角全是常数 c(这里 c = 0)
0 0 * *
0 0 0 *下三角矩阵:所有
压缩策略:存所有非常数部分的元素共
上三角矩阵(非常数部分是
常数
下三角矩阵(非常数部分是
这也是严蔚敏教材给出的经典形式(式 4-14 的下三角情形)。
下三角 · 列优先(对偶推法,供对照):第
并排对照:同样是
三角矩阵(上三角元素全为常数
对称矩阵:
对称矩阵答案是 22,三角矩阵答案是 36。区别只在于"另一半是重复内容还是常数"。 三角矩阵的
三对角矩阵:一个公式管到底
只有满足

图源:殷人昆《数据结构——用面向对象方法与 C++ 描述》(第 2 版)图4.3 三对角矩阵,p140
每行的非零元素数:第
行优先下标公式。 先看中间行(
第
这个公式对第 1 行和第
- 第 1 行:
3i-4代入得 ,比真实值 0 少 1;而行内偏移 , 比真实的 多 1。两处偏差正好抵消,结果仍然正确。 验证: ✓, ✓。 - 第
行: 3i-4代入得 ,正是前 行的真实总数 ✓;行内偏移 对 分别给出 0、1 ✓,与实际一致。 验证( ): , , 而总数 ,下标范围 ✓。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1,1 | 1,2 | 2,1 | 2,2 | 2,3 | 3,2 | 3,3 | 3,4 | 4,3 | 4,4 | 4,5 | 5,4 | 5,5 |
逐个代入
逆映射:已知
验证:
一次正向演算:100 阶三对角矩阵,
两个容易算错的地方:① 第 1 行只有 2 个元素,若把所有行都按 3 个算, 前缀和会得到
稀疏矩阵:三元组表、十字链表,以及"什么时候才划算"(要写结构定义或做选型时展开)
非零元素个数
严蔚敏教材在讲完对称、三角、对角三种特殊矩阵后指出: "在实际应用中还经常会遇到另一类矩阵,其非零元较零元少,且分布没有一定规律,称之为稀疏矩阵。 这类矩阵的压缩存储就要比特殊矩阵复杂。"——"无规律"正是它与前三类的分界线。
三元组表:对每个非零元素
c
typedef struct {
int i, j; // 非零元素的行号、列号
int value; // 元素值
} Triple;
typedef struct {
int rows; // 原矩阵 M 的行数
int cols; // 原矩阵 M 的列数
int nums; // 非零元素个数
Triple data[MAXSIZE];
} TSMatrix;辅助信息为什么必须是"原矩阵的行数和列数",而不是"包含非零元素的行数/列数"? 用反例:一个
三元组的设计原则:辅助信息要做到两件事——① 能完整恢复原矩阵的形状; ② 能完整恢复所有非零元素。三元组负责第二件,行数与列数负责第一件, 两者合起来无冗余、无缺失。
什么时候才划算:三元组把"1 个值"换成了"3 个值"。设每个整数占 1 个单元, 朴素存储
| 非零个数 | 三元组 | 是否划算 |
|---|---|---|
| 100(占 1%) | 303 | 划算,省 97% |
| 1 000(占 10%) | 3 003 | 划算,省 70% |
| 3 333(占 33%) | 10 002 | 持平 |
| 5 000(占 50%) | 15 003 | 反而多花 50% |
三对角矩阵是一个对照:它的非零个数
相对 极少,但因为位置有规律, 用 个单元就够了,连行列号都不用存——有规律时永远比三元组省。
十字链表适合频繁修改(增删非零元素)的稀疏矩阵:
c
typedef struct OLNode {
int row, col; // 行号、列号
int value; // 元素值
struct OLNode *right; // 指向同一行内下一个非零元素
struct OLNode *down; // 指向同一列内下一个非零元素
} OLNode;每个结点同时挂在所在行链表和列链表上——所以叫"十字"。 一个 right 走一遍、某一列沿 down 走一遍;插入新的非零元素在两条链上各找位置后插入, 不需要移动其他元素。劣势是每个结点有 5 个域,单元素开销明显大于三元组。
| 三元组表 | 十字链表 | |
|---|---|---|
| 单个非零元素的开销 | 小( | 大(再多 2 个指针) |
| 插入 / 删除 | 慢(数组要移位, | 快( |
| 按行遍历 | 快(本来就按行序存) | 快(沿 right) |
| 按列遍历 | 慢(要扫全表) | 快(沿 down) |
| 适用场景 | 静态稀疏矩阵 | 频繁修改的稀疏矩阵 |
两者都是稀疏矩阵的压缩存储结构,常被拿来与两个"看着像但不是"的结构对比:
| 结构 | 是不是稀疏矩阵的压缩存储 | 它到底是什么 |
|---|---|---|
| 三元组表 | ✓ | 稀疏矩阵专用 |
| 十字链表 | ✓ | 稀疏矩阵专用(行/列双向遍历) |
| 邻接矩阵 | ✗ | 图的稠密存储, |
| 二叉链表 | ✗ | 二叉树结点的 lchild / rchild 链接结构 |
邻接矩阵的命名容易误导:它的"矩阵"二字让人以为和稀疏矩阵有关。 实际上它就是一个普通的
二维数组,完全不压缩。它本身正是"稀疏矩阵压缩" 要解决的对象之一——稀疏图的邻接矩阵用三元组或十字链表存才高效(图那一章的 邻接表 与 十字链表 就是这个思路)。
为什么要压缩:三组量化数字(想看清收益有多大就展开)
对于
对称矩阵:朴素要 个单元,有用信息只有上三角 个——浪费一半; 三对角矩阵:朴素要 个单元,非零的只有 个——浪费 99.7%; 稀疏矩阵(非零元素 100 个):浪费 99.99%。
严蔚敏教材对压缩存储的表述是:"为多个值相同的元只分配一个存储空间,对零元不分配空间。 假若值相同的元素或者零元素在矩阵中的分布有一定规律,则称此类矩阵为特殊矩阵。"
考点速记
三条会被反复调用的结论:
- 推法只有一条:前
行(列)累加 行(列)内偏移。八个公式都是它在不同形状下的实例。 - 查元素固定三步:判断在不在被存的三角 → 不在就交换下标(对称)或直接取末位(三角)→ 代公式。
- 压缩不是无条件更优:三元组要
个单元,非零占比低于约 才开始省空间。
这一节在真题里被考过的形式(下方「真题练习」逐题对应):绝大多数是同一道题的变形——给定矩阵类型、存储方向和一个元素,问它在一维数组中的下标。要盯住题面里的四个变量:
- 存的是上三角还是下三角(题面会写
之类的范围); - 按行优先还是按列优先;
- 待查元素在不在被存的那一半——若不在,先按对称性交换下标。比如 10 阶对称矩阵只存上三角,问
: 不在上三角,先换成 再算; - 数组下标从 0 还是从 1 开始。
三对角矩阵是另一种问法,用
易错:元素不在被存的三角,却直接代公式。 对称矩阵要先交换下标,三角矩阵要直接取末位常数格。
易错:行优先与列优先的累加对象搞混。 上三角按行存时第
行有 个元素;按列存时第 列有 个元素。累加的是哪个,取决于存储方向。
易错:两个 base 混成一个。 矩阵下标的 base 与数组下标的 base 互相独立,先按熟悉的口径算完,最后一步再
转换,别临场改公式。
易错:看到"矩阵"就往压缩上套。 邻接矩阵是图的稠密存储,
一格不省,不是压缩结构。
教材出处
- 压缩存储的定义("为多个值相同的元只分配一个存储空间,对零元不分配空间")、 特殊矩阵的定义、对称矩阵的压缩存储(存下三角,一维数组
, 式 4-14 给出 与 的一一对应)、三角矩阵的定义与"再加一个存储常数 的存储空间":严蔚敏《数据结构(C 语言版)》(第 2 版)p101「4.4.3 特殊矩阵的压缩存储」 - 上三角矩阵的下标式 4-16、对角矩阵(三对角矩阵) 的定义与图 4.13, 以及稀疏矩阵的定义("其非零元较零元少,且分布没有一定规律"):同书 p102
- 行优先 / 列优先的地址公式与
维映像函数(本篇一切压缩公式的地基):同书 p99–p100 - 图源:殷人昆《数据结构——用面向对象方法与 C++ 描述》(第 2 版)图4.3 三对角矩阵,p140
说明:严蔚敏《数据结构(C 语言版)》(第 2 版)在给出稀疏矩阵的定义后即写明 "这类矩阵的压缩存储就要比特殊矩阵复杂,在此不做讨论", 因此本篇三元组表与十字链表两节没有该书页码可引,其结构定义与优劣对比由本篇自行整理, 不附会页码。
相关知识
多维数组的存储(本篇所有压缩公式的地基,务必先看)| 顺序表(压缩存储的落脚点仍是一维顺序存储)| 邻接矩阵(图的稠密存储,不是压缩结构)| 邻接表、十字链表(同样的"只存存在的边"思想)| 数组与特殊矩阵的压缩存储(拆分前的旧合并视图)