Lesson 0401 · 线性表与顺序表

④ 数据结构与算法 — 第 1 课

三句话速览
  1. 数据结构 = 怎么存 + 能对它做什么操作。
  2. 地址 = 首地址 + i × 元素大小,所以访问快。
  3. 下标访问 O(1),中间插删 O(n),末尾动最快。
上节课(0304):运算符重载与友元,让 a + b、cout << a 认识自己的类。
这节课:进入新阶段。接下来不再只学语法,而是学「怎么把数据组织起来,让操作更快」。这是 CS 专业最重要的课程,也是算法竞赛和大厂面试的核心。

1. 什么是数据结构

数据结构 = 数据的组织方式 + 对这些数据能做什么操作。

同一个「学生名单」,你可以:

选择不同的结构,同样操作的速度可能天差地别。数据结构课就是教你:什么场景选什么结构。

2. 线性表:一串排好队的数据

线性表是最简单的结构:元素一个接一个,有顺序。比如:

[10] -> [20] -> [30] -> [40]

它需要支持这些操作:

线性表有两种经典实现:顺序表(数组/vector) 和 链表。这节课先学顺序表。

3. 顺序表:连续内存放数据

顺序表就是一块连续内存,像一排连号的柜子。你早就在用了:

int a[5] = {10, 20, 30, 40, 50};
vector<int> v = {10, 20, 30, 40, 50};

「顺序」的意思是:第 i 个元素的地址 = 首地址 + i × 元素大小。所以按下标访问非常快。

v[2]             // 直接算出地址,一步拿到
*(v.data() + 2)  // 本质上是这个

这就是它能做到 O(1) 按下标访问的全部原因:地址是算出来的,不是走过去的。链表的第 i 个元素可没法这么算,下一课会看到代价。

4. vector 的中间插入:insert

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> v = {1, 2, 3, 4};

    // 在下标 2 的位置插入 99
    v.insert(v.begin() + 2, 99);
    // v 变成:1 2 99 3 4

    for (int x : v) cout << x << " ";
    return 0;
}

为什么插入后下标 2 后面的元素都往后挪?因为顺序表必须保持连续,所以要先腾位置。

注意 insert 的第一个参数是迭代器,不是下标:要写 v.begin() + 2。

5. vector 的中间删除:erase

vector<int> v = {1, 2, 3, 4, 5};

// 删除下标 2 的元素(值为 3)
v.erase(v.begin() + 2);
// v 变成:1 2 4 5

同样,删除中间元素后,后面的元素要往前挪,补上空位。

为什么中间操作慢:插在中间或删中间,平均要移动后面一半元素。n 个元素就约是 n/2 次移动,所以复杂度是 O(n)。

6. 顺序表操作复杂度速查

操作vector 写法复杂度原因
按下标访问v[i]O(1)直接算地址
末尾插入push_backO(1)通常在末尾有空位
末尾删除pop_backO(1)直接缩短
中间插入insertO(n)后面元素要后移
中间删除eraseO(n)后面元素要前移
查找某个值findO(n)必须一个一个扫
什么时候顺序表好用?
- 经常按下标访问(数组下标)
- 只在末尾增删(栈、动态数组)
- 数据量不大

如果经常在中间插删,链表会更好——下节课见。

7. 坑点清单

8. 小测

Q1. 顺序表最重要的特点是?
Q2. vector 在下标 2 插入一个元素,时间复杂度是?
Q3. 下面哪个操作 vector 最快?

9. 动手练习

题目1:排队名单

读入 n 和 n 个名字,再读入 pos name,把 name 插到下标 pos 处;最后按顺序输出整个名单。
示例:输入 3 Alice Bob Cindy 1 David,输出 Alice David Bob Cindy。
题目2(选做):删掉所有等于 x 的数

输入 n 个数和一个整数 x,删除 vector 中所有等于 x 的元素,输出剩下的数。
注意:循环里调用 erase 时下标要小心,不要跳元素。
卡住时的自查顺序
  1. 读入的名字个数和 n 对得上吗?先打印一遍原名单。
  2. 插入用的是 v.insert(v.begin() + pos, name),还是把下标直接传了进去?
  3. pos 会不会等于 v.size()(插到末尾)?这个位置是合法的。
  4. 输出用空格分隔,最后一个名字后面有没有多一个空格?(多数题不卡,但先看一眼)
  5. 题目2:erase 之后有没有马上 i++?有就多半跳元素了。
  6. 样例输出 Alice David Bob Cindy 对上了吗?没对上就单步打印每一步的 vector。

本课小结

下一课预告
0402 链表 —— 顺序表最怕的「中间插删」正是链表的主场:节点不连续,改几个指针就能插进去,代价是不能按下标直接跳。下一课把两种结构摆在一起对比,你会看到「O(1) 插入」换来的究竟是什么。
本课那句「查找必须一个一个扫」,在链表里会更明显:连「算地址」这条路都没了。

← 上一课:运算符重载与友元 · 课程对照表

书籍 《算法 (第4版)》第 1 章

视频 黑马 C++ 提高篇 vector 容器