跳到主要内容

队列

普通队列按先进先出(FIFO)组织元素。本文负责循环数组、端点操作和两栈实现。

1. 面试核心​

先记住这几条,再读正文
  • FIFO 的范围。 FIFO 队列从尾部入队,从头部出队。它约束的是取出顺序,多线程处理任务时,完成顺序还取决于每项任务的耗时。卡 · 使用边界
  • 循环队列。 循环数组复用已出队元素腾出的槽位。可用 size 区分空与满,也可预留一个槽位来区分两种状态。卡 · 首尾位置与循环数组
  • 两栈队列。 两栈队列只在输出栈为空时倒栈。每个元素只转移一次,因此连续出入队操作的摊销时间为 O(1)。卡 · 两栈队列

2. 首尾位置与循环数组​

数组容量为 capacity,要求 capacity 大于 0。head 指向待取位置,tail 指向待写位置。位置移动使用 next=(index+1)%capacity。

维护 size 时,size=0 为空,size=capacity 为满。满和空都能出现 head=tail,由 size 区分。

另一种实现预留一个槽位。此时 head=tail 为空,next(tail)=head 为满。可用容量为 capacity−1。实现需选定其中一种约定。

3. 入队与出队​

java.util.Queue<Integer> queue = new java.util.ArrayDeque<>();
queue.offer(10);
queue.offer(20);
System.out.println(queue.poll()); // 10
System.out.println(queue.peek()); // 20,仍留在队列中

10 先进入,所以先取出。peek 返回 20,并保留该元素。Java 21 ArrayDeque 的空队列 poll、peek 返回 null。remove、element 在空队列上抛异常,容器拒绝 null 元素。

4. 两栈队列​

入队将元素压入输入栈。出队读取输出栈顶。输出栈为空时,才将输入栈全部倒入输出栈。

依次入队 1、2、3,输入栈顶是 3。倒栈后输出栈顶是 1,恢复 FIFO 顺序。输出栈尚有元素时继续倒栈,会让新元素抢到旧元素前面。

每项至多入输入栈一次、转移一次、出输出栈一次。m 次队列操作总成本为 O(m),所以摊销 O(1)。一次倒栈可移动 n 项,单次最坏仍为 O(n)。

5. 使用边界​

生产持续快于消费时,无界队列会积压。容量有界时,需要定义满队列的处理策略。阻塞、拒绝或丢弃应由调用协议决定。

FIFO 约束取出顺序,多线程的完成顺序受执行耗时影响。ArrayDeque 未提供线程安全或阻塞等待。跨线程传递需使用匹配的并发队列。

按优先级取任务使用堆,其出队规则由优先级决定。

6 · 面试题卡片​

本页题目与参考回答

6.1 FIFO 能保证并发任务按入队顺序完成吗?​

考察重点:取出顺序与完成顺序。

相关内容:使用边界。

参考回答

FIFO 只约束出队顺序。

多个线程的耗时不同,后取任务可先完成。业务要求完成顺序一致时,可串行执行。只要求输出有序时,可按序号缓冲已完成的结果。

6.2 循环队列如何区分空与满?​

考察重点:size 与预留槽位两种约定。

相关内容:首尾位置与循环数组。

参考回答

可用 size 区分:0 为空,capacity 为满。

也可预留一格,用 head=tail 表示空。下一写位置等于 head 时表示满,此时少用一个槽位。

6.3 两栈队列为何只在输出栈为空时倒栈?​

考察重点:FIFO 不变量与摊销成本。

相关内容:两栈队列。

参考回答

输出栈保存更早入队的元素,必须先取完。

为空时倒栈才能让最早的剩余元素位于栈顶。每项只转移一次,所以连续操作摊销 O(1)。

7. 参考资料​