顺序表用Python3定义与增删改查
一、顺序表的设计
核心要素:
| 属性 | 含义 |
|---|---|
data |
底层数组,存元素 |
size |
当前元素个数 |
capacity |
当前容量 |
关键点:
- 元素存在
data[0..size-1],后面的位置空着。 - 插入/删除需要搬移元素。
- 容量不够时扩容。
二、完整代码
python
class SeqList:
"""顺序表(动态数组实现)"""
def __init__(self, capacity=10):
if capacity <= 0:
raise ValueError("容量必须大于 0")
self.data = [None] * capacity # 底层数组
self.size = 0 # 当前元素个数
self.capacity = capacity # 当前容量
# ---------- 内部辅助 ----------
def _resize(self, new_capacity):
"""扩容/缩容:把原数据搬到新数组"""
new_data = [None] * new_capacity
for i in range(self.size):
new_data[i] = self.data[i]
self.data = new_data
self.capacity = new_capacity
def _check_index(self, index):
"""检查访问索引是否合法"""
if index < 0 or index >= self.size:
raise IndexError(f"索引 {index} 越界,合法范围 [0, {self.size - 1}]")
def _check_insert_index(self, index):
"""检查插入索引是否合法(可以等于 size)"""
if index < 0 or index > self.size:
raise IndexError(f"插入索引 {index} 越界,合法范围 [0, {self.size}]")
# ---------- 增 ----------
def insert(self, index, value):
"""在 index 位置插入 value,O(n)"""
self._check_insert_index(index)
# 容量不够,扩容为原来的 2 倍
if self.size == self.capacity:
self._resize(self.capacity * 2)
# 从后往前搬移,给新元素腾位置
for i in range(self.size, index, -1):
self.data[i] = self.data[i - 1]
self.data[index] = value
self.size += 1
def append(self, value):
"""尾部追加,均摊 O(1)"""
self.insert(self.size, value)
def prepend(self, value):
"""头部插入,O(n)"""
self.insert(0, value)
# ---------- 删 ----------
def delete(self, index):
"""删除 index 位置元素,返回被删的值,O(n)"""
self._check_index(index)
value = self.data[index]
# 从前往后搬移,覆盖被删元素
for i in range(index, self.size - 1):
self.data[i] = self.data[i + 1]
self.data[self.size - 1] = None # 末尾置空
self.size -= 1
# 缩容:元素太少时,容量减半(可选优化)
if self.size > 0 and self.size <= self.capacity // 4:
self._resize(max(self.capacity // 2, 10))
return value
def remove(self, value):
"""按值删除第一个匹配项,O(n)"""
index = self.index_of(value)
if index == -1:
raise ValueError(f"值 {value} 不存在")
return self.delete(index)
def pop(self):
"""删除尾部,返回被删的值,O(1)"""
if self.size == 0:
raise IndexError("顺序表为空")
return self.delete(self.size - 1)
def clear(self):
"""清空"""
self.data = [None] * self.capacity
self.size = 0
# ---------- 改 ----------
def update(self, index, value):
"""修改 index 位置的值,O(1)"""
self._check_index(index)
self.data[index] = value
# ---------- 查 ----------
def get(self, index):
"""按索引获取值,O(1)"""
self._check_index(index)
return self.data[index]
def index_of(self, value):
"""按值查找索引,找不到返回 -1,O(n)"""
for i in range(self.size):
if self.data[i] == value:
return i
return -1
def contains(self, value):
"""判断是否包含某值,O(n)"""
return self.index_of(value) != -1
# ---------- 其他 ----------
def is_empty(self):
return self.size == 0
def length(self):
return self.size
def __len__(self):
return self.size
def __getitem__(self, index):
"""支持 lst[index] 访问"""
return self.get(index)
def __setitem__(self, index, value):
"""支持 lst[index] = value"""
self.update(index, value)
def __contains__(self, value):
"""支持 value in lst"""
return self.contains(value)
def __iter__(self):
"""支持 for x in lst"""
for i in range(self.size):
yield self.data[i]
def __str__(self):
return "[" + ", ".join(str(self.data[i]) for i in range(self.size)) + "]"
def __repr__(self):
return f"SeqList({self})"
三、测试代码
python
if __name__ == "__main__":
sl = SeqList()
# ---------- 增 ----------
sl.append(10)
sl.append(20)
sl.append(30)
print(sl) # [10, 20, 30]
sl.insert(1, 15)
print(sl) # [10, 15, 20, 30]
sl.prepend(5)
print(sl) # [5, 10, 15, 20, 30]
# ---------- 查 ----------
print(sl.get(0)) # 5
print(sl[2]) # 15
print(sl.index_of(20)) # 3
print(20 in sl) # True
print(len(sl)) # 5
# ---------- 改 ----------
sl.update(0, 100)
print(sl) # [100, 10, 15, 20, 30]
sl[1] = 200
print(sl) # [100, 200, 15, 20, 30]
# ---------- 删 ----------
sl.delete(0)
print(sl) # [200, 15, 20, 30]
sl.remove(15)
print(sl) # [200, 20, 30]
sl.pop()
print(sl) # [200, 20]
# ---------- 遍历 ----------
for x in sl:
print(x, end=" ") # 200 20
print()
# ---------- 清空 ----------
sl.clear()
print(sl) # []
print(sl.is_empty()) # True
# ---------- 扩容测试 ----------
sl2 = SeqList(capacity=2)
for i in range(10):
sl2.append(i)
print(sl2) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(sl2.capacity) # 16(扩容过程:2→4→8→16)
四、关键操作图解
1. 插入 insert(2, 99)
ini
原数组: [10, 20, 30, 40, _]
size=4
从后往前搬移:
[10, 20, 30, 40, _]
[10, 20, 30, 40, 40] ← data[4] = data[3]
[10, 20, 30, 30, 40] ← data[3] = data[2]
[10, 20, 99, 30, 40] ← data[2] = 99
结果: [10, 20, 99, 30, 40]
size=5
2. 删除 delete(1)
ini
原数组: [10, 20, 30, 40, 50]
size=5
从前往后搬移:
[10, 20, 30, 40, 50]
[10, 30, 30, 40, 50] ← data[1] = data[2]
[10, 30, 40, 40, 50] ← data[2] = data[3]
[10, 30, 40, 50, 50] ← data[3] = data[4]
末尾置空:
[10, 30, 40, 50, None]
结果: [10, 30, 40, 50]
size=4
3. 扩容
ini
容量满: [10, 20, 30, 40] capacity=4
申请新数组(2 倍): [None] * 8
搬移: [10, 20, 30, 40, None, None, None, None]
插入: [10, 20, 30, 40, 50, None, None, None]
capacity=8
五、复杂度总结
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
按索引访问 get |
O(1) | 连续内存,直接定位 |
按索引修改 update |
O(1) | 直接定位 |
尾部追加 append |
O(1) 均摊 | 偶尔扩容,均摊下来 O(1) |
头部插入 prepend |
O(n) | 所有元素后移 |
中间插入 insert |
O(n) | 部分元素后移 |
删除 delete |
O(n) | 部分元素前移 |
按值查找 index_of |
O(n) | 遍历 |
按值删除 remove |
O(n) | 查找 + 删除 |
六、顺序表 vs 链表
| 对比项 | 顺序表 | 链表 |
|---|---|---|
| 内存 | 连续 | 分散 |
| 随机访问 | O(1) | O(n) |
| 头部插入/删除 | O(n) | O(1) |
| 尾部插入 | O(1) 均摊 | O(n)(无尾指针) |
| 中间插入/删除 | O(n) | O(n)(定位)+ O(1)(改指针) |
| 按值查找 | O(n) | O(n) |
| 扩容 | 需要搬移 | 不需要 |
| 内存开销 | 可能浪费 | 额外指针开销 |
| 缓存友好 | 好 | 差 |
选择原则:
- 频繁随机访问 → 顺序表。
- 频繁头部/中间增删 → 链表。
- 大多数场景 → Python 的
list就够用。
七、常见坑
| 坑 | 说明 | 正确做法 |
|---|---|---|
| 插入位置越界 | 插入可以等于 size | 判断 index > size |
| 访问位置越界 | 访问必须小于 size | 判断 index >= size |
删除后忘记 size -= 1 |
长度错误 | 删完减 1 |
| 删除后忘记置空 | 内存泄漏 | data[size-1] = None |
| 扩容忘记搬移 | 数据丢失 | 逐个复制 |
| 缩容太频繁 | 性能抖动 | 用 1/4 阈值,不要用 1/2 |
用 list 模拟时混淆 size 和 len |
概念混淆 | size 是逻辑长度,capacity 是物理容量 |
为什么缩容阈值用 1/4 而不是 1/2?
- 如果用 1/2,在临界点反复插入删除会导致频繁扩容缩容(抖动)。
- 用 1/4,留出缓冲,避免抖动。
八、一句话总结
| 问题 | 答案 |
|---|---|
| 顺序表是什么? | 用连续内存存储的线性表 |
| 核心属性? | data(数组)、size(元素个数)、capacity(容量) |
| 增? | insert、append、prepend,容量不够扩容 |
| 删? | delete、remove、pop,搬移元素 |
| 改? | update,O(1) |
| 查? | get(O(1))、index_of(O(n)) |
| 最大优势? | 随机访问 O(1) |
| 最大劣势? | 中间插入/删除 O(n) |
| 和 list 关系? | Python 的 list 就是动态顺序表 |
| 和链表区别? | 顺序表连续、随机访问快;链表分散、增删快 |
核心记住:顺序表 = 连续内存 + size + capacity。随机访问 O(1),中间增删 O(n),容量不够要扩容。手写一遍能彻底理解 Python list 的底层原理。