Skip to content

模型Model

数组

Array

以连续整数下标支持随机访问的有限序列结构。

形式陈述 ​

长度为自然数 n、元素类型为 V 的数组,在抽象层是一个按连续整数下标组织的有限序列:

A:[0,n)→V,[0,n)={0,1,…,n−1}.

n=0 时有空数组,但不存在合法元素下标。相同的值可以出现在多个位置;数组长度计算位置数,不是不同值的个数。

固定长度数组的核心操作是读取和原位写入。对合法下标 i,读取返回 A(i);写入 v 得到新状态 A′,其契约为

A′(j)={v,j=i,A(j),j≠i.

后一行是重要的保持条件:写入一个位置不应改变其他位置。写入不增加长度;在中间插入一个新元素属于另一个操作,必须说明后续元素如何移动、长度如何变化。

常见实现把数组放在连续内存中。若首槽地址为 b,每槽占 s 个字节,则

addr(A[i])=b+is.

在地址和下标可装入常数个机器字、字操作及访问为常数成本的 Word-RAM 模型中,这给出 O(1) 下标访问。抽象序列本身并不强制连续布局;常数时间结论来自具体表示和计算模型。

直觉

数组用“位置可以算出来”代替“沿链接寻找位置”。下标 i 不是从头走 i 步的指令,而是参与一次地址计算的数。等宽槽位让所有位置遵循相同公式,因此尾部元素并不比头部元素更难定位。

这也解释了访问与插入的差别。把第 i 个位置的内容改掉,只需使用原槽位;在它前面增加一个位置,却会改变整个后缀的下标。容量足够时,在长度 n 的序列位置 i 插入要搬动 n−i 个原元素,再写入新值,成本为 Θ(n−i+1)。尾部插入因此便宜,头部插入则必须移动整个序列;“随机访问为常数时间”没有对这些更新作出承诺。

下标、值与地址
例子与边界

位置和值是两件事 ​

对于 A=[7,2,9,4,6,1],A[3]=4,而不是 3。执行写入 A[3]←8 后,结果是 [7,2,9,8,6,1],长度仍为 6,其余五个位置保持不变。这里下标从零开始;最后一个位置是 n−1,A[n] 已超出定义域。

若改为在下标 2 前插入 8,结果应是 [7,2,8,9,4,6,1]。容量足够时,连续数组仍须把原后缀 [9,4,6,1] 右移:先复制 A[5] 到 A[6],再依次执行 A[4]→A[5]、A[3]→A[4]、A[2]→A[3],最后写 A[2]=8 并增加长度。

倒序搬迁保持一个清晰的不变量:已经处理的右侧后缀位于最终位置,尚未处理的左侧原值仍完好。如果改为从左向右,第一次 A[3]=A[2] 就覆盖了下一步还要读取的旧 A[3]=4。删除一个位置时依赖方向反过来,应从左向右把后缀向前搬。移动方向来自旧值的读取依赖,而不是记忆一条与操作无关的规则。

二维表如何落到一维内存 ​

设二维数组有 m 行、n 列,以行优先方式存储,则

addr(A[i,j])=b+(in+j)s,0≤i<m,0≤j<n.

例如 2×3 数组的 (1,2) 位置前面有一整行三个槽位,再加本行两个槽位,偏移为 5s。列优先表示则是 b+(jm+i)s。两个公式表示同一个二维索引集合,但相邻内存位置的方向不同;逐行还是逐列扫描,会因此影响局部性。

数组视图还可能带步长。例如从一个一维数组抽取下标 0,2,4,…,第 j 个视图元素的地址是 b+2js。它仍支持公式定位,却不在底层占据连续相邻的槽位。视图若共享存储,修改其中元素还可能同时改变原数组;这必须由接口明确说明。

等宽的是槽位,不一定是对象内容 ​

字符串数组可以在每个等宽槽位中保存一个引用。读取第 i 个引用仍可为常数时间,但复制或比较整个字符串的成本取决于字符串长度。类似地,任意精度整数的加法不是因为存放在数组里,就自动变成一个单位成本操作。

抽象模型也不替具体语言规定越界行为。检查后报错、返回带失败标记的结果以及不提供安全访问,都属于不同契约。数学表达 A:[0,n)→V 只说明哪些下标有定义,并没有为 A[n] 指定一个合法值。

长度与容量 ​

动态数组另外维护容量 C≥n。前 n 个槽位属于逻辑序列,剩余槽位是预留空间。尾部追加在容量未满时只写入一个槽位;容量满时,通常分配更大区块并复制旧元素。

若容量按倍数增长,从空数组开始连续追加 N 次,扩容复制量由几何和控制。例如翻倍时总复制量小于 2N,加上 N 次新元素写入,总代价为 O(N)。因此追加的摊还成本为 O(1),但触发扩容的某一次操作仍可能花费 Θ(n)。中间插入的后缀移动没有被这个论证消除。

推论与应用

数组正确性通常分为两层:抽象层规定读取、写入、长度及失败行为;表示层证明每个合法下标映到正确槽位,并且不破坏其他槽位。带容量的实现还需保持 0≤n≤C,视图实现则需保持偏移和步长对应的地址范围。这是抽象数据类型与表示不变量的一次具体配合。

与链表相比,数组擅长按位置访问和连续扫描;链表在已经持有合适节点引用时,可以通过局部链接修改完成部分插入删除。链表的“局部修改快”不包括找到第 i 个节点的时间,数组的“访问快”也不包括自动维护任意有序位置的时间。

二叉堆进一步利用规则形状,把节点关系编码为下标关系;前缀和利用顺序扫描,把区间聚合转化为少数位置的读取。两者节省工作的方式不同,但都建立在位置语义明确、访问契约可靠的基础上。

参考资料
  • Pat Morin,Open Data Structures: An Introduction,Athabasca University Press,2013,Chapter 2 与 §2.1 ArrayStack:数组表示、移位、扩容和摊还分析。
  • Thomas H. Cormen 等,Introduction to Algorithms,4th ed.,2022,§10.1 与动态表的摊还分析章节:基本表示与成本模型;进一步阅读。
关系图谱61 个相邻概念 · 4 类关系

拖动节点调整位置。

显示关系

显示:依赖

  1. 前置三跳
  2. 前置二跳
  3. 前置一跳
  4. 当前条目
  5. 后续一跳
  6. 后续二跳
  7. 后续三跳
文字版关系按与当前条目的最短距离分组
类型化关系