跳到主要内容数据结构 - 多维数组的超平面视角:从索引到地址的映射 | 极客日志C++算法
数据结构 - 多维数组的超平面视角:从索引到地址的映射
综述由AI生成从超平面视角解析多维数组的逻辑结构与物理存储,阐述了索引与内存地址的映射关系。通过降维切片和子空间概念,推导了行优先存储下的地址计算公式及权重系数递推方法。文章结合混合进制数系统类比,并提供了基于 C++ 模板的 N 维数组顺序存储实现代码,帮助理解多维数组在连续内存中的寻址原理。
嘘28 浏览 数据结构 - 多维数组的超平面视角:从索引到地址的映射
核心思想
多维数组在逻辑上是嵌套的子空间结构,在物理上是一段连续的内存。
寻址的本质:每一维的索引跳过若干个完整的子空间;未满的那个子空间,进入下一维继续定位,直到 0 维。
什么是多维数组?
多维数组是一个定义在离散笛卡尔积上的函数:
A: D \to V
其中:
D = {0,...,b_1-1} × {0,...,b_2-1} × ... × {0,...,b_n-1}(定义域)
V = 数据类型的值域(如 int, float)
- b_m = 第 m 维的大小(该维度拆出的子空间个数)
- 维数 = 笛卡尔积的阶数(坐标分量的个数)
- 元素由一个 n 元组 (i_1, i_2, ..., i_n) 唯一确定
逻辑结构:降维切片
几何图解(三维)
k轴 (b₃=4) ↑ │ ┌─────────┐ │ ╱ ╱│ │ ╱ 平面 1 ╱ │ │┌─────────┐ │ A[2][3][4] ││ 平面 0 │ │ ││ │ │ 总共 2×3×4 = 24 个元素 ││ │ ╱ 连续存放在内存中 │└─────────┘ └──────────→ j轴 (b₂=3) ╱ ╱ i轴 (b₁=2)
降维过程:每次固定一个轴,维度降低一级
对于 A[b₁][b₂][b₃],访问 A[i][j][k] 的过程:
第 1 步:固定 i(选第几个平面)— 3 维 → 2 维
i=0 → 取第 0 个平面(12 个元素)
i=1 → 取第 1 个平面(12 个元素)
跳过 i 个完整的 (n-1) 维 子空间 [3][4]
第 i+ 个平面未满 → 进入内部继续定位
1
第 2 步:固定 j(选第几行)— 2 维 → 1 维
j=0 → 取第 0 行(4 个元素)
j=1 → 取第 1 行(4 个元素)
j=2 → 取第 2 行(4 个元素)
跳过 j 个完整的 (n-2) 维 子空间 [4]
第 j+1 行未满 → 进入内部继续定位
第 3 步:固定 k(选第几个元素)— 1 维 → 0 维
k=0,1,2,3 → 跳过 k 个 (n-3) 维 子空间(单个元素) → 到达 0 维,定位完成
数学表述
D_3 = {0,...,b_1-1} × {0,...,b_2-1} × {0,...,b_3-1} (3 维)
D_2 = {0,...,b_2-1} × {0,...,b_3-1} (2 维平面)
D_1 = {0,...,b_3-1} (1 维行)
每次索引 = 固定一个坐标轴 = 超平面切片 = 维度降低一级
物理存储:连续线性内存
内存布局(行优先): [a₀₀₀][a₀₀₁][a₀₀₂][a₀₀₃][a₀₁₀][a₀₁₁]...[a₁₀₀][a₁₀₁]...
'平面'、"行"只是逻辑标签,物理上是一串连续的字节。
地址计算公式
核心逻辑:逐层跳过 + 逐层细化
以 A[2][3][4] 访问 A[1][2][3] 为例。
每一维的索引,跳过若干个完整的子空间;未满的那个子空间,进入下一维继续定位,直到 0 维:
第 1 步:i=1 跳过 1 个完整的 (n-1) 维 子空间 [3][4] → 第 2 个平面未满,进入内部继续定位
第 2 步:j=2 跳过 2 个完整的 (n-2) 维 子空间 [4] → 第 3 行未满,进入内部继续定位
第 3 步:k=3 跳过 3 个完整的 (n-3) 维 子空间(单个元素) → 到达 0 维,定位完成
偏移量 = 每一步跳过的子空间个数 × 该子空间的大小,逐层累加:
偏移 = i × size(n-1 维子空间) + j × size(n-2 维子空间) + k × size(n-3 维子空间)
= 1 × (3×4) + 2 × (4) + 3 × (1) = 12 + 8 + 3 = 23 个元素
转成字节:LOC = LOC_0 + 23 × L = LOC_0 + 92
子空间大小的递推
每一级子空间的大小 = 下一级的个数 × 下一级的大小:
| 子空间维度 | 大小(元素数) | 递推关系 | 具体值 |
|---|
| 0 维(元素) | 1 | 基础单位 | 1 |
| 1 维(行) | b_3 × 1 = b_3 | b_3 个元素 | 4 |
| 2 维(平面) | b_2 × b_3 | b_2 个行 | 12 |
c_3 = 1 × L = 4, c_2 = b_3 × c_3 = 16, c_1 = b_2 × c_2 = 48
三维公式
LOC = LOC_0 + i × c_1 + j × c_2 + k × c_3
每一项的含义:该维索引(跳过几个子空间) × 该维子空间的字节大小 = 跳过的字节数。
推广到 n 维
对于 A[b₁][b₂]...[bₙ],访问 A[i₁][i₂]...[iₙ]:
LOC = LOC_0 + ∑_{m=1}^{n} c_m · i_m
LOC = LOC_0 + ∑{m=1}^{n} (∏{p=m+1}^{n} b_p) · i_m · L
m 是求和的循环变量(从第 1 维遍历到第 n 维),不是数组索引 j 或 k。
∏_{p=m+1}^{n} b_p 就是第 m 维子空间的大小(元素个数),乘以 L 得到字节数。
逻辑始终不变:从最外层到最内层,每层跳过 i_m 个完整子空间,未满的进入下一层继续定位。
权重系数 c_m(递推形式)
c_n = L, c_m = b_{m+1} × c_{m+1} (m = n-1, n-2, ..., 1)
其中 b_{m+1} 是第 m 维的子空间内部有多少个下一级子空间,c_{m+1} 是每个下一级子空间的字节大小。
b_m 与 c_m 的关系
| 符号 | 含义 | 类比(书架) |
|---|
| b_m | 第 m 维有几个子空间(结构形状) | 第 m 级有几格 |
| c_m | 第 m 维走一步跳多少字节(寻址步长) | 第 m 级一格有多宽 |
| i_m | 第 m 维选第几个(具体索引) | 第 m 级选第几格 |
| L | 递推起点,最底层单个元素的字节大小 | 每本书的厚度 |
具体例子:A[2][3][4],sizeof(int) = 4
c_3 = L = 4(1 个元素 = 4 字节)
c_2 = b_3 × c_3 = 4 × 4 = 16(1 行有 b_3=4 个元素 = 16 字节)
c_1 = b_2 × c_2 = 3 × 16 = 48(1 层有 b_2=3 行 = 48 字节)
LOC = LOC_0 + 1 × 48 + 2 × 16 + 3 × 4 = LOC_0 + 48 + 32 + 12 = LOC_0 + 92
- 1×48:跳过 1 个完整平面
[3][4](进入第 1 层)
- 2×16:平面未满,跳过 2 整行
[4](进入第 2 行)
- 3×4:行未满,跳过 3 个元素(到达目标)
子空间的计数公式
k 维子空间的个数
N(k 维子空间) = ∏_{i=1}^{n-k} b_i
含义:从第 1 维到第 (n-k) 维依次固定,第 i 维沿轴跨越 b_i 个位置,可以固定在 0 到 b_i-1 的任意一个位置,所有组合的总数就是 ∏_{i=1}^{n-k} b_i。
每个 k 维子空间的大小
Size(k 维子空间) = ∏_{i=n-k+1}^{n} b_i
含义:固定前 (n-k) 维后,剩下的后 k 维自由变化,元素总数 = 后 k 个维度大小之积。下标从 n-k+1 开始是因为取的是后 k 个维度。
验证关系
N_k × Size_k = ∏_{i=1}^{n} b_i = 总元素数 ✓
示例:A[2][3][4]
| 维度 | 子空间个数 | 每个大小 | 总元素数 |
|---|
| 3 维(整体) | 1 | 2×3×4=24 | 24 |
| 2 维(平面) | b_1=2 | b_2×b_3=3×4=12 | 24 |
| 1 维(行) | b_1×b_2=2×3=6 | b_3=4 | 24 |
| 0 维(点) | b_1×b_2×b_3=24 | 1 | 24 |
与混合进制数系统的类比
数组地址计算本质上是**混合进制(Mixed-radix)**的位置表示。
253 = 2 × 10^2 + 5 × 10^1 + 3 × 10^0
数组偏移 A[i][j][k](每维"基数"不同:b_1, b_2, b_3):
offset = i × (b_2 × b_3) + j × b_3 + k × 1
i = floor(offset / (b_2 × b_3))
j = floor((offset mod (b_2 × b_3)) / b_3)
k = offset mod b_3
代码实现
template<typename T>
class SeqArray {
public:
explicit SeqArray(std::initializer_list<uint64_t> bounds);
explicit SeqArray(const std::vector<uint64_t>& bounds);
~SeqArray() = default;
const T& get(std::initializer_list<uint64_t> indices) const;
void set(std::initializer_list<uint64_t> indices, const T& value);
[[nodiscard]] uint64_t dimensions() const;
[[nodiscard]] uint64_t bound(uint64_t dim) const;
[[nodiscard]] uint64_t total_size() const;
private:
std::unique_ptr<T[]> _data;
std::vector<uint64_t> _bounds;
std::vector<uint64_t> _constants;
uint64_t _total;
uint64_t _offset(const std::vector<uint64_t>& indices) const;
void _init_constants();
};
template<typename T>
SeqArray<T>::SeqArray(std::initializer_list<uint64_t> bounds)
: SeqArray(std::vector<uint64_t>(bounds)) {}
template<typename T>
SeqArray<T>::SeqArray(const std::vector<uint64_t>& bounds)
: _bounds(bounds) {
_init_constants();
_data = std::make_unique<T[]>(_total);
}
template<typename T>
const T& SeqArray<T>::get(std::initializer_list<uint64_t> indices) const {
return _data[_offset(indices)];
}
template<typename T>
void SeqArray<T>::set(std::initializer_list<uint64_t> indices, const T& value) {
_data[_offset(indices)] = value;
}
template<typename T>
uint64_t SeqArray<T>::dimensions() const {
return _bounds.size();
}
template<typename T>
uint64_t SeqArray<T>::bound(uint64_t dim) const {
return _bounds[dim];
}
template<typename T>
uint64_t SeqArray<T>::total_size() const {
return _total;
}
template<typename T>
uint64_t SeqArray<T>::_offset(const std::vector<uint64_t>& indices) const {
if (indices.size() != _bounds.size())
throw std::invalid_argument("维度不匹配");
uint64_t offset = 0;
for (uint64_t i = 0; i < indices.size(); ++i) {
if (indices[i] >= _bounds[i])
throw std::out_of_range("索引越界");
offset += indices[i] * _constants[i];
}
return offset;
}
template<typename T>
void SeqArray<T>::_init_constants() {
uint64_t n = _bounds.size();
_constants.resize(n);
_constants[n - 1] = 1;
for (uint64_t i = n - 1; i > 0; --i) {
_constants[i - 1] = _bounds[i] * _constants[i];
}
_total = _bounds[0] * _constants[0];
}
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online