首页/数据结构/线性表/顺序表的实现 🔗 在 Obsidian 中打开
数据结构 · 线性表

顺序表的实现

重要度 ⭐⭐⭐⭐⭐ 线性表顺序表顺序存储
速查
顺序表用一段地址连续的存储单元存储元素,支持随机访问 O(1):第 $i$ 个元素地址 $\text{LOC}(a_i)=\text{LOC}(a_1)+(i-1)\times \text{sizeof}$。插入/删除需移动元素,平均 $O(n)$。

核心概念

顺序表是用一段地址连续的存储单元依次存储线性表中的数据元素。逻辑上相邻的元素在物理存储上也相邻。

┌───┬───┬───┬───┬───┬───┬───┬───┐
│ a₁│ a₂│ a₃│ a₄│ a₅│   │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┘
  0   1   2   3   4   5   6   7  (下标)
  ↑                   ↑
 base              last

C 语言定义

#define MaxSize 50
typedef struct {
    ElemType data[MaxSize];  // 静态分配
    int length;              // 当前长度
} SqList;

// 动态分配
#define InitSize 100
typedef struct {
    ElemType *data;          // 动态数组指针
    int MaxSize, length;     // 最大容量和当前长度
} SeqList;

地址与随机访问

第 $i$ 个元素的地址:$\text{LOC}(a_i) = \text{LOC}(a_1) + (i-1) \times \text{sizeof}(\text{ElemType})$

下标从 0 开始:$\text{LOC}(a[i]) = \text{LOC}(a[0]) + i \times \text{sizeof}(\text{ElemType})$

随机访问按位序定位只需一次地址计算,时间复杂度 O(1)——这是顺序表相对链表最大的优势。

基本操作

1. 插入操作 ListInsert(&L, i, e)

在第 $i$ 个位置插入元素 $e$($1 \leq i \leq \text{length}+1$):

bool ListInsert(SqList *L, int i, ElemType e) {
    if (i < 1 || i > L->length + 1)  // 位置合法性
        return false;
    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;
}
插入复杂度最好 O(1)(表尾)、最坏 O(n)(表头)、平均 O(n/2) = O(n)

2. 删除操作 ListDelete(&L, i, &e)

删除第 $i$ 个位置的元素($1 \leq i \leq \text{length}$):

bool ListDelete(SqList *L, int i, ElemType *e) {
    if (i < 1 || i > L->length)
        return false;
    *e = L->data[i-1];                // 取出被删元素
    for (int j = i; j < L->length; j++)  // 前移
        L->data[j-1] = L->data[j];
    L->length--;
    return true;
}
删除复杂度最好 O(1)(表尾)、最坏 O(n)(表头)、平均 O((n-1)/2) = O(n)

3. 按值查找 LocateElem(L, e)

int LocateElem(SqList L, ElemType e) {
    for (int i = 0; i < L.length; i++)
        if (L.data[i] == e)
            return i + 1;  // 返回位序
    return 0;
}

时间复杂度:平均 O(n)。

手算示例

例 1:插入操作模拟

顺序表 $L = \{1, 3, 5, 7, 9\}$,在第 3 个位置插入元素 4:

  1. 检查:位置 3 合法,长度 5 < MaxSize
  2. 后移:data[5]=data[4]=9data[4]=data[3]=7data[3]=data[2]=5
  3. 插入:data[2] = 4
  4. 长度:$length = 6$
  5. 结果:{1, 3, 4, 5, 7, 9}

移动次数:3 次($i=3$ 时,移动 $n-i+1=5-3+1=3$ 个元素)。

例 2:平均移动次数计算

长度为 $n$ 的顺序表,插入操作平均移动次数:

$$E_{\text{insert}} = \sum_{i=1}^{n+1} p_i \times (n-i+1) = \frac{1}{n+1} \sum_{i=1}^{n+1} (n-i+1) = \frac{n}{2}$$

常见考法

考法 1在长度为 n 的顺序表中插入一个元素,平均需要移动多少个元素?→ n/2 个
考法 2 · 地址计算顺序表起始地址 1000,每个元素占 4 字节,求第 10 个元素地址 → $1000 + (10-1) \times 4 = 1036$。
考法 3给出插入/删除代码框架,填写循环条件和移动方向。(插入从后往前、删除从前往后)
考法 4 · 对比顺序表和链表在插入删除上的效率对比 → 顺序表 O(n) vs 链表 O(1)(已知位置时)。

易错点

易错清单
  1. 位序 vs 下标:位序从 1 开始,下标从 0 开始。第 $i$ 个元素对应 data[i-1]
  2. 插入位置合法范围是 $1 \leq i \leq length+1$,不是 $1 \leq i \leq length$。
  3. 删除位置合法范围是 $1 \leq i \leq length$。
  4. 移动方向:插入时从后往前移,删除时从前往后移。
  5. 满表判断:插入前必须检查 length >= MaxSize

核心结论

操作最好最坏平均
插入O(1)O(n)O(n)
删除O(1)O(n)O(n)
按位查找O(1)O(1)O(1)
按值查找O(1)O(n)O(n)

顺序表核心优势:随机访问 O(1);存储密度高(无需额外指针);缓存友好(连续存储)。

顺序表核心劣势:插入删除需移动大量元素 O(n);静态分配需预估大小,动态分配需复制。

记忆存储密度 = 1(只存数据元素本身,不需要额外指针)。

记忆卡片

顺序表插入的时间复杂度?
最好 O(1)、最坏 O(n)、平均 O(n)。
存储密度为何高?
只存储数据元素,无需额外指针,密度 = 1。
第 i 个元素地址公式?
$LOC(a_i) = LOC(a_1) + (i-1)\times \text{sizeof}$。
插入为何从后往前移?
避免覆盖未移动的元素,保证每次移动时目标位置空闲。

交互动画 · 插入与删除时的元素移动

10 31 52 73 94 5 6 length=5
长度为 5 的顺序表 {1, 3, 5, 7, 9},下标从 0 开始
选择操作,观察元素移动方向与次数
插入把第 i 位起的元素从后往前后移(先动最右端,避免覆盖);删除把其后的元素从前往后前移。

相关知识点

singly-linked-list-implementation linear-list-applications array-vs-linked-list

↑ 以上为站内 HTML 相对链接(纯网页可浏览);本页右上「在 Obsidian 中打开」跳回源笔记。