数据结构核心总结:线性表、链表、栈与队列
数据结构核心总结:线性表、链表、栈与队列 🚀
数据结构是计算机存储、组织数据的方式。好的数据结构能够显著提升程序的运行效率。本文将系统总结线性表、链表、栈和队列这四种最基础且应用最广泛的数据结构,帮助你建立完整的知识体系。
📖 目录
线性表基础
什么是线性表?
线性表(Linear List) 是由 n 个具有相同特性的数据元素组成的有序序列。它是数据结构中最简单、最基本的一种结构。
线性表的基本特征:
- 存在唯一的”第一个”元素
- 存在唯一的”最后一个”元素
- 除第一个元素外,每个元素都有且仅有一个前驱元素
- 除最后一个元素外,每个元素都有且仅有一个后继元素
graph LR
A[元素1] --> B[元素2]
B --> C[元素3]
C --> D[元素4]
E[元素n-1] --> F[元素n]
style A fill:#90EE90
style F fill:#FFB6C1
线性表的抽象数据类型
线性表的操作主要包括:
| 操作 | 说明 |
|---|---|
initList() |
初始化线性表 |
length() |
获取线性表长度 |
get(i) |
获取第 i 个位置的元素 |
insert(i, e) |
在第 i 个位置插入元素 e |
delete(i) |
删除第 i 个位置的元素 |
isEmpty() |
判断线性表是否为空 |
clear() |
清空线性表 |
flowchart TD
A[开始] --> B{线性表是否为空?}
B -->|是| C[执行特殊处理]
B -->|否| D[查找目标位置]
D --> E{位置有效?}
E -->|是| F[执行插入/删除操作]
E -->|否| G[返回错误]
F --> H[长度变化]
H --> I[结束]
C --> I
G --> I
顺序表与链表
线性表有两种主要的存储方式:顺序存储和链式存储。
flowchart TD
A[线性表] --> B[顺序表]
A --> C[链表]
B --> D[使用连续内存空间存储]
C --> E[使用指针连接各元素]
D --> F[随机访问 O1]
E --> G[插入删除 On]
F --> H[空间固定]
G --> I[空间动态]
顺序表(Sequential List)
顺序表使用一段连续的存储单元依次存储线性表中的元素。
优点:
- 🔥 随机访问能力强,查询速度快(时间复杂度 O(1))
- 🔥 缓存命中率高,因为内存连续
缺点:
- ❌ 插入和删除操作需要移动大量元素(时间复杂度 O(n))
- ❌ 存储空间固定,大小受限制
- ❌ 需要预先分配足够的连续内存空间
链表(Linked List)
链表通过指针将一系列元素连接起来,每个元素包含数据域和指针域。
优点:
- ✅ 插入和删除操作不需要移动元素(时间复杂度 O(1))
- ✅ 不需要预先分配固定大小的空间
- ✅ 可以动态扩展
缺点:
- ❌ 不支持随机访问,查询需要遍历(时间复杂度 O(n))
- ❌ 每个元素需要额外的指针空间
- ❌ 缓存命中率较低
单向链表
单向链表结构
单向链表(Singly Linked List)是链表中最简单的一种,每个节点包含数据域和指向下一个节点的指针。
graph LR
subgraph 单向链表
A[HEAD] --> B[Node1|Data: 10|Next: →]
B --> C[Node2|Data: 20|Next: →]
C --> D[Node3|Data: 30|Next: →]
D --> E[NULL]
end
style A fill:#87CEEB
style E fill:#FFB6C1
Java 实现单向链表
1 | /** |
单向链表操作图解
flowchart LR
subgraph 插入操作
A1[原链表] --> A2["在 Node2 后插入 NodeX"]
A2 --> A3["Node2.next = NodeX"]
A3 --> A4["NodeX.next = Node3"]
end
subgraph 删除操作
B1[原链表] --> B2["删除 Node2"]
B2 --> B3["Node1.next = Node3"]
B3 --> B4["释放 Node2"]
end
双向链表与循环链表
双向链表
双向链表(Doubly Linked List)的每个节点包含数据域、前驱指针和后继指针。
graph LR
A[NULL] <--> B[Node1|Data: 10|Prev: ←|Next: →]
B <--> C[Node2|Data: 20|Prev: ←|Next: →]
C <--> D[Node3|Data: 30|Prev: ←|Next: →]
D <--> E[NULL]
style A fill:#FFB6C1
style E fill:#FFB6C1
双向链表 Java 实现
1 | /** |
循环链表
循环链表(Circular Linked List)是链表的一种变形,最后一个节点的指针指向头节点,形成环状结构。
graph LR
A[HEAD] --> B[Node1]
B --> C[Node2]
C --> D[Node3]
D --> A
style A fill:#87CEEB
循环链表的特点:
- 🔄 没有 null 指针,所有节点形成闭环
- 🔄 从任意节点出发,都可以遍历整个链表
- 🔄 常用于需要循环遍历的场景(如约瑟夫问题)
栈
什么是栈?
栈(Stack) 是一种特殊的线性表,它遵循 LIFO(Last In First Out) 原则——后进先出。
flowchart TD
A[入栈 Push] --> B["栈 Stack"]
B --> C[出栈 Pop]
A2["元素 D"] -->|压入| B
A3["元素 C"] -->|压入| B
A4["元素 B"] -->|压入| B
A5["元素 A"] -->|压入| B
B -->|弹出| R1["元素 D"]
style B fill:#FFD700
栈的基本操作
| 操作 | 说明 | 时间复杂度 |
|---|---|---|
push(e) |
将元素压入栈顶 | O(1) |
pop() |
弹出栈顶元素 | O(1) |
peek() |
查看栈顶元素 | O(1) |
isEmpty() |
判断栈是否为空 | O(1) |
size() |
获取栈的大小 | O(1) |
Java 栈实现
1 | /** |
栈的应用场景
mindmap
root((栈的应用))
函数调用
调用栈
递归实现
表达式求值
中缀转后缀
后缀表达式计算
括号匹配
有效括号判断
浏览器前进后退
历史记录
撤销操作
编辑器撤销
典型应用:
- 函数调用栈 - 程序语言的函数调用管理
- 括号匹配 - 检验表达式中的括号是否合法
- 表达式求值 - 中缀表达式转后缀表达式
- 深度优先搜索(DFS) - 图和树的遍历
括号匹配实例
1 | /** |
队列
什么是队列?
队列(Queue) 是一种特殊的线性表,它遵循 FIFO(First In First Out) 原则——先进先出。
flowchart LR
A[入队 Enqueue] --> B["队列 Queue"]
B --> C[出队 Dequeue]
A2["元素 A"] -->|加入队尾| B
A3["元素 B"] -->|加入队尾| B
A4["元素 C"] -->|加入队尾| B
B -->|移除队首| R1["元素 A"]
style B fill:#87CEEB
队列的基本操作
| 操作 | 说明 | 时间复杂度 |
|---|---|---|
enqueue(e) |
将元素加入队尾 | O(1) |
dequeue() |
移除队首元素 | O(1) |
front() |
查看队首元素 | O(1) |
rear() |
查看队尾元素 | O(1) |
isEmpty() |
判断队列是否为空 | O(1) |
Java 队列实现
1 | /** |
循环队列原理图
flowchart LR
subgraph 循环队列示意图
direction TB
A1["front=0"] --> A2["rear=4"]
A2 --> A3["capacity=8"]
end
subgraph 队列状态
direction LR
B1[0] --> B2[1]
B2 --> B3[2]
B3 --> B4[3]
B4 --> B5["A" fill:#90EE90]
B5 --> B6["B" fill:#90EE90]
B6 --> B7["C" fill:#90EE90]
B7 --> B8["D" fill:#90EE90]
end
队列的应用场景
mindmap
root((队列的应用))
任务调度
线程池
打印机队列
广度优先搜索
BFS遍历
层级遍历
消息队列
异步通信
限流削峰
缓存策略
FIFO缓存
页面置换
典型应用:
- 任务调度 - 操作系统中的进程/线程调度
- 广度优先搜索(BFS) - 图和树的层级遍历
- 消息队列 - 异步通信、流量削峰
- 缓存策略 - LRU、LFU 等缓存算法
BFS 遍历实例
1 | /** |
总结对比
四种数据结构对比
flowchart TD
A[数据结构选择] --> B{主要操作是什么?}
B -->|只需要在一端操作| C{需要 FIFO 还是 LIFO?}
C -->|LIFO| D[栈 Stack]
C -->|FIFO| E[队列 Queue]
B -->|需要在中间插入删除| F{需要随机访问?}
F -->|是| G[顺序表 ArrayList]
F -->|否| H[链表 LinkedList]
D --> I["时间复杂度 O(1)"]
E --> I
G --> J["查询 O(1) / 插入删除 O(n)"]
H --> K["查询 O(n) / 插入删除 O(1)"]
复杂度对比表
| 数据结构 | 访问 | 插入 | 删除 | 空间 |
|---|---|---|---|---|
| 顺序表 | O(1) | O(n) | O(n) | O(n) |
| 单向链表 | O(n) | O(1)* | O(1)* | O(n) |
| 双向链表 | O(n) | O(1)* | O(1)* | O(n) |
| 栈 | - | O(1) | O(1) | O(n) |
| 队列 | - | O(1) | O(1) | O(n) |
*注:O(1) 的前提是已经找到目标位置,否则需要 O(n) 的查找时间。
选择建议
graph LR
A["频繁查询"] --> D["顺序表 / ArrayList"]
B["频繁插入删除"] --> E["链表 / LinkedList"]
C["先进先出"] --> F["队列 Queue"]
D --> G["需要平衡"]
E --> G
F --> G
G --> H["LinkedList 实现 Queue"]
选择要点:
- 🔥 查询多、修改少 → 选择
ArrayList(顺序表) - 🔥 插入删除多、查询少 → 选择
LinkedList(链表) - 🔥 需要 LIFO → 选择
Stack - 🔥 需要 FIFO → 选择
Queue
📚 最后
线性表、链表、栈、队列是数据结构中最基础的概念,看似简单,却支撑着整个计算机世界的运行。理解它们的原理和适用场景,是成为优秀程序员的必经之路。
希望本文对你有所帮助!如果有任何问题,欢迎留言讨论 💬
原创不易,转载请注明出处!
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 旅人小站!