1. 线性表的顺序存储
- 定义:用一段地址连续的存储空间依次存储线性表中的元素。
- 优点:
- 随机存取:通过下标可以直接访问任意元素,时间复杂度为 O(1)。
- 缺点:
- 插入/删除效率低:需要移动大量元素,时间复杂度为 O(n)。
2. 线性表的链式存储
- 定义:通过指针将逻辑上相邻的元素链接起来,物理地址不一定连续。
- 常见形式:
- 单链表:只能从头结点开始顺序遍历。
- 循环单链表:可以从任意结点出发遍历整个链表。
- 循环双链表:支持双向遍历,也能从任意结点出发。
一、线性表的顺序存储(顺序表)
存储结构细节
顺序表的存储可以类比数组,其地址连续性体现在:若第一个元素地址为 Loc(e₁)
,每个元素占用 k
个存储单元,则第 i
个元素的地址为:
Loc(eᵢ) = Loc(e₁) + (i-1)×k
这也是实现“随机存取”的底层原理。
优缺点补充
- 优点:
- 存储密度高(无需额外空间存储指针)。
- 遍历效率高(通过下标连续访问)。
- 缺点:
- 初始空间分配困难(分配过大浪费空间,过小易溢出)。
- 动态扩容成本高(通常需申请新空间并复制原元素,时间复杂度 O(n))。
典型应用场景
- 频繁查询、元素数量固定或变化不大的场景(如数组、数据库中的固定长度字段)。
二、线性表的链式存储(链表)
存储结构细节
链表中每个元素(称为“结点”)包含两部分:
- 数据域:存储元素本身的值。
- 指针域:存储相邻结点的地址(单链表只有一个指针,双链表有两个指针)。
常见形式对比
类型 | 指针特点 | 遍历方向 | 首尾连接性 |
---|---|---|---|
单链表 | 每个结点含一个“后继指针” | 只能从表头到表尾 | 表头无前驱,表尾无后继 |
循环单链表 | 同单链表,但表尾指针指向表头 | 从任意结点开始循环遍历 | 首尾相连成环 |
循环双链表 | 每个结点含“前驱指针”和“后继指针”,表尾后继指向表头,表头前驱指向表尾 | 可双向循环遍历 | 首尾相连成环 |
优缺点补充
- 优点:
- 插入/删除灵活(只需修改指针,无需移动元素,时间复杂度 O(1),前提是找到目标位置)。
- 动态扩容方便(无需预分配空间,按需申请结点)。
- 缺点:
- 不能随机存取(访问第
i
个元素需从表头遍历,时间复杂度 O(n))。 - 存储密度低(指针域占用额外空间)。
- 不能随机存取(访问第
典型应用场景
- 频繁插入/删除、元素数量动态变化的场景(如链表式队列、栈、邻接表存储图)。
三、顺序表与链表的核心区别
对比维度 | 顺序表 | 链表 |
---|---|---|
物理存储 | 地址连续 | 地址不一定连续 |
访问方式 | 随机存取(下标) | 顺序存取(遍历指针) |
插入/删除效率 | 平均 O(n)(需移动元素) | 平均 O(1)(只需修改指针) |
空间利用率 | 高(无指针开销) | 低(有指针开销) |
适用操作类型 | 多查询,少增删 | 多增删,少查询 |
通过以上对比可以看出,两种存储结构各有侧重,实际开发中需根据具体业务场景(如操作频率、数据规模)选择合适的方式。例如,ArrayList(Java)底层是顺序表,LinkedList 底层是双向链表,正是基于这些特性设计的。
顺序存储和链式存储在执行一次插入操作时,核心差异体现在 “是否需要整体搬移元素” 以及 “所需时间复杂度” 两个方面:
比较维度 | 顺序存储(数组) | 链式存储(链表) |
---|---|---|
插入前的准备 | 必须先检查是否已满(需要扩容则还要申请一块更大的连续空间并整体复制原数据)。 | 只要系统还有可用内存,随时可插入,无需整体搬迁。 |
插入过程 | 在指定位置插入前,需要把该位置之后的所有元素整体向后移动一位。 | 只需修改少量指针:让新结点指向后继,让前驱指向新结点。 |
时间复杂度 | O(n) —— 最坏要移动 n 个元素。 | O(1) —— 若已定位到插入位置,仅需常数次指针修改;若需先查找位置,则查找为 O(n),但插入本身仍是 O(1)。 |
额外空间 | 可能需要整块重新分配,浪费或复制大量已有元素。 | 每次插入只需额外申请一个结点,不会产生整块搬迁。 |
一句话总结:
顺序存储插入是“搬家式”的批量移动,链式存储插入是“插针式”的指针调整。