在Python里,列表本身就是可变序列,借助它提供的方法可以很轻量地模拟栈和队列这两种基础线性结构。栈遵循后进先出,队列遵循先进先出,二者核心差异只在于元素的取出位置。理解列表底层连续存储的特性,才能写出既正确又高效的代码。

用列表实现栈
栈的操作非常单纯:只能在某一端压入和弹出。Python列表的尾部追加和删除都是 amortized O(1),因此把列表末尾当作栈顶是最合理的做法。我们只需使用 append 方法压栈,用 pop 方法弹栈,不需要指定索引。
下面给出一个简单的栈封装示例,包含判空、大小和查看栈顶的辅助方法,避免在业务代码里直接散落列表操作。
class ListStack:
def __init__(self):
self._data = []
def push(self, item):
# 尾部追加,时间复杂度O(1)
self._data.append(item)
def pop(self):
if self.is_empty():
raise IndexError('pop from empty stack')
# 弹出尾部元素,时间复杂度O(1)
return self._data.pop()
def peek(self):
if self.is_empty():
return None
return self._data[-1]
def is_empty(self):
return len(self._data) == 0
def size(self):
return len(self._data)
if __name__ == '__main__':
s = ListStack()
s.push(10)
s.push(20)
print(s.pop()) # 输出20
print(s.peek()) # 输出10
这种实现的优点是直观、无需引入标准库之外的东西,而且由于列表缓存局部性好,小规模数据下速度极快。缺点是一旦列表前期容量不足,append 可能触发底层数组扩容拷贝,不过均摊之后影响可以忽略。
如果你的栈需要频繁跨线程使用,列表本身不是线程安全的,要在外层加锁。但在单线程算法题或脚本工具里,列表栈已经足够。
用列表实现队列
队列要求从一端进、另一端出。若直接在列表头部用 pop(0) 或 insert(0, item) 来模拟,每次操作都会让后面所有元素在内存中向前或向后平移一位,时间复杂度退化为 O(n)。当数据量到十万级,这种写法会明显卡顿。
一种折中方案是仍然用列表,但把尾部当队尾、头部当队头,并接受 O(n) 的弹出成本;另一种更好的做法是改用 collections.deque,它在两端操作都是 O(1)。下面先用纯列表写出版本,再给出 deque 版本做对比。
class ListQueue:
def __init__(self):
self._data = []
def enqueue(self, item):
# 尾部入队
self._data.append(item)
def dequeue(self):
if not self._data:
raise IndexError('dequeue from empty queue')
# 头部出队,会触发元素平移,O(n)
return self._data.pop(0)
def is_empty(self):
return len(self._data) == 0
# 推荐:使用deque实现高效队列
from collections import deque
class DequeQueue:
def __init__(self):
self._data = deque()
def enqueue(self, item):
self._data.append(item)
def dequeue(self):
if not self._data:
raise IndexError('dequeue from empty queue')
return self._data.popleft()
def is_empty(self):
return len(self._data) == 0
从代码可以看出,DequeQueue 只是把底层存储换成了 deque,接口完全不变,但 dequeue 从 O(n) 降到 O(1)。在任务调度、广度优先搜索等需要频繁进出队的场景,这种替换能大幅降低延迟。
如果出于某些限制必须用列表且数据量较大,也可以用环形缓冲思路:维护 head 和 tail 指针,配合固定长度列表复用槽位,避免整体搬移。不过那已经接近手写 deque,日常开发中直接引用标准库更稳妥。
性能与选型对比
为了更清楚地看到差异,我们把两种结构在常见操作上的表现整理成表。这里的复杂度指单次操作随数据规模 n 的变化趋势。
| 结构 | 入栈/入队 | 出栈 | 出队(列表头) | 随机访问 |
|---|---|---|---|---|
| 列表栈 | O(1) | O(1) | 不适用 | O(1) |
| 列表队列(pop(0)) | O(1) | 不适用 | O(n) | O(1) |
| deque队列 | O(1) | 不适用 | O(1) | O(1)近似 |
可以看到,列表栈在功能与效率上都没有短板;列表硬模拟队列则在出队上存在性能陷阱。如果你的代码只是几十个元素的小缓冲,用 pop(0) 也无伤大雅,但写成公共组件时最好用 deque 或注明复杂度。
另外要注意,列表和 deque 都不是线程安全的,多线程生产消费请配合 queue.Queue 或加锁,不要因为底层用了 deque 就误以为原子。
常见误区与小结
一个常见误区是认为列表的 insert(0, x) 和 pop(0) 跟尾部一样快,实际上连续存储决定了头部变动必然搬移。另一个误区是把栈和队列的方法混用,比如用列表栈却从索引0取元素,破坏了封装语义。
实践中,建议把数据结构用类包一层,对外只暴露 push、pop 或 enqueue、dequeue,这样未来从列表切换到 deque 甚至自定义环形队列时,调用方无需改动。Python标准库已经提供了完备原语,理解列表实现有助于看懂这些原语的成本边界,而不是重复造轮子。