队列接口与实现
看完你能分清 Queue 各方法的语义差异,并亲手说清每种实现背后的取舍
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
队列接口与实现
看完你能分清 Queue 各方法的语义差异,并亲手说清每种实现背后的取舍
排队里的队列
奶茶店高峰期排长队:店员永远先做队首那单,新来的只准排在队尾。没人插队、没人久等被跳过——这种「先来先服务」的秩序,正是队列的工作方式。
排队顺序对应入队顺序,取餐口对应队首出队
队列的接口
上一页把队列比作排队:先来的先走。但作为使用者,我们不关心队伍怎么摆,只关心能对它做什么——就像点餐只需要会看菜单,不必进后厨。这些「能做的操作」就是接口。
菜单上的菜名对应接口方法,后厨做法对应内部实现
接口长什么样
用 Python 真实代码展示队列接口:五个操作各自的签名与职责。
enqueue 只动尾部、dequeue 只动头部,这就是 FIFO 在接口层面的体现;dequeue 和 peek 都先做空队列检查。
队列的典型实例
三种典型场景最终都汇入同一个队列结构,读图时注意数据都从队尾进、队头出。
接口自测
向队列依次入队 1、2、3,再出队两次,每次出队得到的依次是?
实现即选择
前面见过队列接口的样子:入队、出队、看队头。但接口只说「做什么」,没说「怎么存」。就像同一份菜单,后厨用普通灶还是中央厨房,上菜速度完全不同——实现的选择决定性能。
菜单是接口,后厨做法是实现,菜名一样出菜方式不同
链表实现队列
链表用头指针+尾指针,让两端操作都只需改几个指针。
数组实现的陷阱
用朴素数组出队,每移除队首都要求余元素前移,看空间和时间怎么被浪费。
循环数组与假溢出
从下标 0 出发看数组如何首尾相连:入队指针 tail 走到末尾后取模回绕,复用 head 让出的空位。
循环队列的判满边界
两行判满条件对比:牺牲一格 vs 维护计数,看清指针重合时的歧义。
空与满时 head、tail 都可能重合,单靠指针无法区分。牺牲一格让 tail 永远停在 head 前一格,判满只看相邻关系;计数法则完整利用空间,代价是多维护一个变量。
两种实现对比
两种实现都能实现队列,但时间、空间和硬件表现差异很大,选错会埋下性能隐患。
- 入队出队均 O(1),无扩容
- 每节点多存一个指针,空间开销大
- 节点散落内存,缓存不友好
- 均摊 O(1),扩容时单次 O(n)
- 只存数据本身,空间紧凑
- 内存连续,缓存命中率高
要点收束
- ✓接口是约定,实现是手段,二者解耦
- ✓调用方只依赖接口,换实现不改业务代码
- ✓链表实现无容量忧虑,数组实现更省内存
- ✓数组实现的关键是循环数组解假溢出
- ✓判满判空是循环队列最容易错的边界
实现选择自测
已知最大长度恒为 100 的固定任务池,需频繁入队出队且空间绝不浪费,最合适的实现是?
课后思考
先自己想一会儿,再对照参考答案——想的过程比答案本身更值钱。
参考答案顺着判满边界想:front==rear 既可能是空也可能是满,必须打破这个歧义。牺牲一位是拿空间换简单;计数是拿每次入队出队的一次额外操作换空间。
参考答案数组吞吐高、内存连续,但容量固定;消息量不可预估时链表按需分配更稳。真实需求如持久化、背压控制、动态扩容策略,都可能推翻课堂上的简单结论。
参考答案接口变大,实现负担变重:数组两头都要 O(1) 就得改成循环双端,边界条件翻倍;链表需双向链表。接口每加一个操作,都在重新分配各实现的优势场景。