Appearance
顺序表(数组)
2026 大纲 二(二)线性表的实现 · 1 顺序存储。
一条约定,推出全部性质
顺序表只有一条约定:第
先看它带来的红利。既然位置是固定的,那么第
再看它的账单。插入和删除会破坏这条约定:在中间插一个元素,后面每个元素的"第
还有一处贯穿全篇的错位要先说好:位序从 1 数,数组下标从 0 数,第 data[i-1]。顺序表代码写错,多半就错在这个 1 上。
先看一眼
拖着插入和删除各走一遍,重点盯元素搬动的方向和搬了几个:在表头插入时整个表都要后移,在表尾插入则一个都不用动。这个"位置不同、代价差
存储结构
c
#define MaxSize 50 // 静态分配:容量上限,编译期确定
typedef struct {
ElemType data[MaxSize]; // 定长数组,直接嵌在结构体里
int length; // 当前表长(元素个数),不是容量
} SqList;
图源:殷人昆《数据结构——用面向对象方法与 C++ 描述》(第 2 版)图 2.2,p46。图中
last记的是最后一个元素的下标(空表last = -1),下文代码用length记元素个数(空表length = 0)。两套等价(last = length - 1),但同一份代码里必须只用一套。
容量也可以在运行期再定,即动态分配:结构体里只存一个基地址,空间用 malloc 在堆上申请,满了就另申请一块更大的、把老数据整体搬过去。
⚠️ 动态分配不是链式存储。 这是概念上最容易滑过去的一处:动态分配只把"什么时候申请这块连续空间"从编译期推迟到了运行期,申请到手的仍然是一整块地址连续的空间,第
个元素仍在第 个物理位置上,地址公式照样成立,随机存取照样 。真正区分顺序存储与链式存储的是"逻辑相邻是不是等于物理相邻",不是"空间什么时候申请"。
动态分配的结构定义、扩容代码与均摊分析(想弄清"均摊常数时间"怎么来的就展开)
c
#define InitSize 10
typedef struct {
ElemType *data; // 只存基地址,真正的空间用 malloc 在堆上申请
int MaxSize; // 当前容量,可变
int length; // 当前表长
} SeqList;
void InitList(SeqList &L) {
L.data = (ElemType *)malloc(sizeof(ElemType) * InitSize);
L.MaxSize = InitSize;
L.length = 0;
}
// 扩容:另申请一块更大的空间,把老数据整体复制过去,再释放老空间
void IncreaseSize(SeqList &L, int len) {
ElemType *p = L.data;
L.data = (ElemType *)malloc(sizeof(ElemType) * (L.MaxSize + len));
for (int i = 0; i < L.length; i++)
L.data[i] = p[i]; // 逐个搬到新空间,这一步是 O(n)
L.MaxSize += len;
free(p); // 释放老空间,否则内存泄漏
}均摊分析:单次扩容要复制全部
平摊到
⚠️ 如果扩容策略是"每次固定加
四个基本操作
c
// 按位查找:直接算地址,一步到位
bool GetElem(SqList L, int i, ElemType &e) {
if (i < 1 || i > L.length) return false; // 位序从 1 起算
e = L.data[i - 1]; // 位序 i ↔ 下标 i-1
return true;
}
// 按值查找:从头逐个比较,返回位序(不是下标);0 是非法位序,可作失败标志
int LocateElem(SqList L, ElemType e) {
for (int i = 0; i < L.length; i++)
if (L.data[i] == e) return i + 1;
return 0;
}
// 插入:在第 i 个位置之前插入 e,第 i ~ 第 n 个元素依次后移一位
bool ListInsert(SqList &L, int i, ElemType e) {
if (i < 1 || i > L.length + 1) return false; // 插入的合法范围比删除多一个 n+1
if (L.length >= MaxSize) return false; // 表满,静态分配下无法插入
for (int j = L.length; j >= i; j--) // 必须从后往前,否则前面的值被覆盖
L.data[j] = L.data[j - 1];
L.data[i - 1] = e;
L.length++;
return true;
}
// 删除:删掉第 i 个元素,第 i+1 ~ 第 n 个元素依次前移一位
bool ListDelete(SqList &L, int i, ElemType &e) {
if (i < 1 || i > L.length) return false; // 删除的合法范围没有 n+1
e = L.data[i - 1]; // 先取出被删元素的值
for (int j = i; j < L.length; j++) // 从前往后,把后面的往前挪
L.data[j - 1] = L.data[j];
L.length--;
return true;
}两个循环的方向相反,这不是巧合,也不用死记。判据只有一句:先动的那一格,必须是马上要被覆盖掉的那一格。 插入时空位在后面,所以从后往前搬;删除时空位在前面,所以从前往后搬。方向写反的后果很有辨识度——同一个值会被复制满整段区间。
还有两个合法范围的细节:插入是
⚠️ 按值查找是
,表有序也仍是 ——上面的 LocateElem根本没利用有序性。要降到必须换算法,即折半查找,而折半查找成立的前提恰恰是顺序表的随机存取。
插入与删除四个边界的逐格自查(第一次学、或想手动模拟时展开)
插入的边界自查:
(插在表尾之后):循环 j从递减到 ,一次都不执行,直接写 data[n] = e,移动 0 个元素,正确;(插在表头):循环执行 次,全部元素后移,移动 个元素,代价最大; - 空表插入
: length = 0,循环不执行,data[0] = e,length变 1,正确。
删除的边界自查:
(删表尾):循环 j从到 ,不执行,移动 0 个元素; (删表头):循环执行 次,移动 个元素,代价最大; - 空表删除:
length = 0,触发合法性判断,返回 false,正确。
删除后落在表外的残值不需要清除——length 已经减 1,任何合法操作都不会读到它。这是"删除即改 length"的直接体现。
一次插删要搬多少个元素
上一节的边界自查已经透出规律:搬多少,只取决于插在哪。 插在表尾之后一个不用搬,插在表头要搬
🔴 两者为什么差一点,有确定的来历:插入有
个合法位置、删除只有 个;对应地,插入的移动次数取遍 ,删除取遍 。两个式子其实在算同一件事——一个从 0 到某上界的等差数列的平均值,上界分别是 和 。记住这个结构比记两个式子可靠:换成问"最坏移动多少次"也能立刻答上来(分别是 和 )。
按值查找的平均比较次数同理,等概率下是
⚠️ 等概率是一个显式前提,不是恒成立的事实。 题目若改成"只在表尾插入",平均移动次数是 0;改成"只在表头插入",就是
。看到"平均"二字,先找它的概率假设。
三个平均值的完整求和推导(想自己推一遍而不是记结论就展开)
插入:插入位置
在等概率假设下
删除:删除位置
按值查找:设查找第
插入与删除的对读
| 插入 | 删除 | |
|---|---|---|
| 合法位置数 | ||
| 在位置 | ||
| 移动次数的取值集合 | ||
| 最大移动次数(发生在 | ||
| 平均移动次数 |
考点速记
三条会被反复调用的结论:
- 一切都来自那条约定——第
个元素放在第 个物理位置。随机存取是红利,插删要移动元素是账单。 - 按位查找
,按值查找、插入、删除都是 ,辅助空间一律 。 - 存储密度恒为 1,这是顺序表相对链表唯一的空间优势;一旦按上限预分配出大片空闲区,优势就被抵消。
这一节在真题里被考过的形式(下方「真题练习」逐题对应):
- 哪个操作的平均时间是
:四个选项摆出查找、插入、删除、按位取值,只有按位取值是 。题干里"有序表"三个字是干扰——顺序查找不会因为有序变快,而折半查找是 ,仍不是 。 - 哪些操作必然引起元素移动:在空间充足、保持元素相对顺序的前提下,表头插入、表头删除必然移动,表尾插入、表尾删除一个都不用动。判断依据就是"约定被破坏在哪一段"。
- 换成链式存储后,哪些算法会变慢:凡是依赖随机存取的算法都会退化——希尔排序要按增量跳着取元素,堆排序要按下标算孩子结点,链式存储下这两步都得从头走。
- 大题里当数组用:循环左移、找主元素、两个升序序列的中位数、最小未出现正整数等,题面通常同时限定时间和空间,要求在数组上做到
时间、 辅助空间。
易错:插入与删除的移动方向写反。 插入从后往前、删除从前往后。判据是"先动的那一格必须是马上要被覆盖掉的那一格";写反的症状是同一个值复制满整段。
易错:把"动态分配"当成链式存储。 它拿到的仍是一整块连续空间,随机存取照样
。区分顺序与链式的标准是"逻辑相邻是否等于物理相邻"。
易错:位序与下标错位 1。 位序从 1 起、下标从 0 起,第
个在 data[i-1];插入合法范围,删除只到 。
教材出处
- 顺序表插入算法与"在第
个位置插入需从第 个元素起依次后移、共 个元素", 以及按值查找平均查找长度 的推导: 严蔚敏《数据结构(C 语言版)》(第 2 版),p27,2.4 节。 - 插入平均移动次数
的完整推导 (式 2-5、2-6):同书 p28。 - 删除算法与平均移动次数
的完整推导 (式 2-7、2-8):同书 p29。该页末尾同时指出,顺序表随机存取的优点"也造成了这种存储 结构的缺点:在做插入或删除操作时,需移动大量元素"。 - 顺序表存储密度为 1、单链表整型结点存储密度为 0.5 的对比:同书 p41,2.6 节。
相关知识
线性表的基本概念(选型判据)|单链表(链式存储主篇)|折半查找(成立前提正是随机存取)|顺序栈|循环队列|快速排序|堆排序