【数据结构精讲】线性表四大核心:数组 / Vector、栈、队列、链表(原理 + 模板 + 避坑 + 选型)
·
一、线性表基础定义
线性表是n 个数据元素的有限序列,元素之间满足「一对一」的前驱后继关系,除首尾元素外,每个元素有且仅有一个前驱和一个后继。
本文聚焦线性表的四大核心实现:数组 / Vector、栈、队列、链表,从底层原理、工程模板、典型应用、避坑要点四个维度系统梳理,内容均为可直接落地的干货。
二、数组与动态数组(Vector)
1. 底层原理
- 静态数组:编译期分配连续内存,长度固定,支持 O(1) 随机访问,但无法动态扩容。
- 动态数组(
std::vector):C++ 标准库实现的动态数组,底层为连续内存,支持自动扩容(扩容因子通常为 1.5/2 倍),兼顾随机访问与动态存储。
2. 核心 API 与时间复杂度
| 操作 | 接口 | 时间复杂度 | 说明 |
|---|---|---|---|
| 尾插 | push_back(x) |
均摊 O (1) | 扩容时为 O (n),均摊后为常数 |
| 尾删 | pop_back() |
O(1) | 仅修改尾指针,无内存释放 |
| 随机访问 | v[i] / at(i) |
O(1) | at() 会做越界检查,[] 无检查 |
| 插入(指定位置) | insert(pos, x) |
O(n) | 需移动后续元素腾出位置 |
| 删除(指定位置) | erase(pos) |
O(n) | 需移动后续元素覆盖删除位置 |
| 扩容优化 | reserve(n) |
O(1) | 预分配内存,避免多次扩容开销 |
3. 工程模板(含二维数组)
#include <vector>
using namespace std;
// 一维vector基础操作
vector<int> v;
v.reserve(100); // 预分配100个元素空间,优化扩容
v.push_back(1); // 尾插
v.pop_back(); // 尾删
for (int x : v) { /* 范围for遍历 */ }
// 二维vector(模拟表格/矩阵)
vector<vector<int>> grid(5, vector<int>(5, 0)); // 5×5初始化为0
grid[0][1] = 10; // 随机访问二维元素
4. 典型应用与避坑
- 应用场景:点名问题、寄包柜模拟、批量数据存储与遍历、矩阵运算。
- 避坑要点:
- 越界访问:
v[i]需保证i < v.size(),否则行为未定义。 - 迭代器失效:
insert/erase操作会使后续迭代器失效,需更新迭代器。 - 扩容开销:频繁
push_back大数据时,务必先reserve预分配内存。
- 越界访问:
三、栈(Stack)
1. 底层原理
栈是 受限线性表,遵循「后进先出(LIFO)」原则,仅允许在 栈顶 进行插入(入栈)和删除(出栈)操作,底层可由数组 / Vector 或链表实现。
2. 核心 API 与时间复杂度
| 操作 | 接口 | 时间复杂度 | 说明 |
|---|---|---|---|
| 入栈 | push(x) |
O(1) | 栈顶插入 |
| 出栈 | pop() |
O(1) | 栈顶删除,无返回值 |
| 取栈顶 | top() |
O(1) | 仅读取栈顶元素 |
| 判空 | empty() |
O(1) | 判断栈中是否有元素 |
3. 手动实现模板(数组版・静态)
const int MAXN = 1005;
int st[MAXN];
int top = 0; // 栈顶指针,初始为0表示空栈
// 入栈
void push(int x) {
st[++top] = x;
}
// 出栈
void pop() {
if (!empty()) top--;
}
// 取栈顶
int get_top() {
return st[top];
}
// 判空
bool empty() {
return top == 0;
}
4. 动态数组实现栈(Vector・推荐)
优点:无容量上限、自动扩容、代码极简、竞赛 / 教学首选
#include <vector>
using namespace std;
vector<int> st;
// 入栈
void push(int x) {
st.push_back(x);
}
// 出栈
void pop() {
if (!st.empty())
st.pop_back();
}
// 获取栈顶
int top() {
return st.back();
}
// 判空
bool empty() {
return st.empty();
}
// 获取元素个数
int size() {
return st.size();
}
5. 典型应用与避坑
- 应用场景:
- 括号匹配:判断字符串中括号是否成对合法。
- 后缀表达式求值:逆波兰表达式的计算核心结构。
- 递归转非递归:模拟函数调用栈,实现 DFS 等递归算法。
- 避坑要点:
- 空栈访问:
top()/pop()前必须判断!empty(),否则程序崩溃。 - 栈溢出:数组实现的栈需控制最大容量,避免栈顶指针越界。
- 空栈访问:
四、队列(Queue)
1. 底层原理
队列是 受限线性表,遵循「先进先出(FIFO)」原则,仅允许在 队尾插入(入队)、队头删除(出队),底层可由数组 / Vector 或链表实现。
2. 核心 API 与时间复杂度
| 操作 | 接口 | 时间复杂度 | 说明 |
|---|---|---|---|
| 入队 | push(x) |
O(1) | 队尾插入 |
| 出队 | pop() |
O(1) | 队头删除,无返回值 |
| 取队头 | front() |
O(1) | 读取队头元素 |
| 取队尾 | back() |
O(1) | 读取队尾元素 |
| 判空 | empty() |
O(1) | 判断队列是否为空 |
3. 手动实现模板(循环队列版・静态)
const int MAXN = 1005;
int q[MAXN];
int front = 0, rear = 0;
// 入队
bool push(int x) {
if ((rear + 1) % MAXN == front) return false;
rear = (rear + 1) % MAXN;
q[rear] = x;
return true;
}
// 出队
bool pop() {
if (empty()) return false;
front = (front + 1) % MAXN;
return true;
}
// 判空
bool empty() {
return front == rear;
}
// 取队头
int get_front() {
return q[(front + 1) % MAXN];
}
4. 动态数组实现队列(Vector 版)
适合教学演示,直观易懂
#include <vector>
using namespace std;
vector<int> q;
// 队尾入队
void push(int x) {
q.push_back(x);
}
// 队头出队
void pop() {
if (!q.empty())
q.erase(q.begin());
}
// 获取队头
int front() {
return q[0];
}
// 获取队尾
int back() {
return q.back();
}
// 判空
bool empty() {
return q.empty();
}
5. 高效动态队列(deque 版・工程推荐)
#include <deque>
using namespace std;
deque<int> q;
// 入队 O(1)
q.push_back(x);
// 出队 O(1)
q.pop_front();
// 队头
q.front();
// 队尾
q.back();
6. 典型应用与避坑
- 应用场景:
- 广度优先搜索(BFS):树 / 图的层序遍历核心结构。
- 模拟排队问题:超市结账、进程调度、消息队列。
- 约瑟夫问题:循环队列的经典应用。
- 避坑要点:
- 空队列访问:
front()/pop()前必须判断!empty(),否则程序崩溃。 - 假溢出问题:普通数组队列易出现「队尾满但队头有空」的假溢出,循环队列通过取模运算解决。
- 空队列访问:
五、链表(Linked List)
1. 底层原理
链表是 非连续内存存储 的线性表,通过指针连接节点,每个节点包含数据域和指针域。核心优势为插入 / 删除操作 O(1) 时间复杂度,劣势为随机访问需 O(n) 遍历。常见分类:单链表、双向链表、循环链表。
2. 核心操作模板(单链表)
struct Node {
int data;
Node* next;
Node(int x) : data(x), next(nullptr) {}
};
Node* head = nullptr;
// 头插法(O(1))
void insert_head(int x) {
Node* newNode = new Node(x);
newNode->next = head;
head = newNode;
}
// 指定值删除(O(n))
void delete_node(int x) {
Node *cur = head, *pre = nullptr;
while (cur && cur->data != x) {
pre = cur;
cur = cur->next;
}
if (!cur) return;
if (!pre) head = cur->next;
else pre->next = cur->next;
delete cur;
}
// 遍历
void traverse() {
Node* cur = head;
while (cur) {
// 处理 cur->data
cur = cur->next;
}
}
3. 典型应用与避坑
- 应用场景:
- 频繁插入删除场景:排队记录、动态任务调度。
- 多项式运算:用链表存储多项式的项,实现加减运算。
- 优化约瑟夫问题:循环链表实现,无需移动元素。
- 避坑要点:
- 内存泄漏:手动实现链表需用
delete释放删除的节点。 - 断链问题:插入 / 删除操作时指针指向错误,导致链表断裂。
- 空指针访问:遍历链表时需判断
cur != nullptr,避免空指针异常。
- 内存泄漏:手动实现链表需用
六、四大线性表对比与选型指南
| 数据结构 | 存储方式 | 随机访问 | 插入 / 删除(指定位置) | 核心特性 | 适用场景 |
|---|---|---|---|---|---|
| 数组 / Vector | 连续内存 | O(1) | O(n) | 随机访问高效 | 数据规模已知、频繁查询、矩阵运算 |
| 栈 | 连续 / 非连续 | 不支持 | O (1)(仅栈顶) | 后进先出(LIFO) | 括号匹配、递归模拟、表达式求值 |
| 队列 | 连续 / 非连续 | 不支持 | O (1)(队头 / 队尾) | 先进先出(FIFO) | BFS、排队模拟、进程调度 |
| 链表 | 非连续内存 | O(n) | O(1) | 插入删除高效 | 频繁增删、数据规模未知、动态任务管理 |
七、线性表学习进阶路线
- 基础阶段:掌握数组 / Vector 的 API 与迭代器,理解线性表的基本概念。
- 受限线性表:通过栈和队列的生活化案例,理解 LIFO/FIFO 特性,掌握 STL 实现与手动实现。
- 进阶阶段:掌握链表的节点定义、插入删除操作,理解链表与数组的优劣对比。
- 应用阶段:结合 BFS、DFS、括号匹配、约瑟夫问题等真题,巩固四大线性表的实际应用。
更多推荐




所有评论(0)