← 返回题目列表

什么是循环队列?如何解决「假溢出」并判断队空队满?

高频 中等 第 16 / 30 题 更新于 2026/07/29
队列循环队列数组

简化版

普通数组队列出队后,队头前面的空间没法再用,明明还有空位却提示满了,这叫假溢出。循环队列让队头队尾指针取模回绕(index + 1) % capacity),把数组当成首尾相接的环,空出来的位置能循环利用。判空判满常用留一个空位rear == front 为空,(rear + 1) % capacity == front 为满。

详细版

用数组 + 两个指针 front(队头)、rear(队尾),入队写 rear 位置后 rear = (rear+1)%cap,出队读 front 位置后 front = (front+1)%cap

问题:rear == front 既可能是「空」也可能是「满」,无法区分。三种常见解法:

  1. 牺牲一个空位(最常用):故意不让队列填满,留一格空。
    • 队空:front == rear
    • 队满:(rear + 1) % cap == front
    • 实际能存 cap − 1 个元素。
  2. 额外记一个 size 计数size == 0 为空、size == cap 为满,能存满 cap 个,但多维护一个变量。
  3. 加一个布尔标志 / 用 count:思路同上。
class MyCircularQueue {
    int[] a; int front, rear, cap;
    MyCircularQueue(int k) { cap = k + 1; a = new int[cap]; } // 多留一格
    boolean enQueue(int v) {
        if (isFull()) return false;
        a[rear] = v; rear = (rear + 1) % cap; return true;
    }
    boolean deQueue() {
        if (isEmpty()) return false;
        front = (front + 1) % cap; return true;
    }
    boolean isEmpty() { return front == rear; }
    boolean isFull()  { return (rear + 1) % cap == front; }
}

完整版教学

一、假溢出是怎么来的

用固定数组做队列,队头出队时如果只是 front++,那么数组左边被出队腾出的格子再也用不到了。反复入队出队后,rear 到了数组末尾、front 前面却空着一堆——这时入队会判「越界/满」,但实际还有空间。这种「有空位却报满」就是假溢出,本质是「线性数组用一次就作废前面的空间」。

二、循环队列如何消除假溢出

把数组想象成一个:指针走到末尾就用取模 % cap 回到开头。这样队头出队腾出的前面格子,队尾入队时能绕回来复用,空间循环利用,不再有假溢出。核心动作就是指针 +1 后对容量取模

三、判空判满为什么是难点

回绕之后,front == rear 出现在两种时刻:一开始(空),或者刚好绕一圈填满(满)。同一个条件对应两种状态,必须想办法区分

  • 留一个空位:让「满」时 rear 停在 front 前一格,于是「满」是 (rear+1)%cap == front,「空」才是 front == rear。代价是浪费一格(能存 cap−1 个)。
  • 额外计数 size:直接判 size==0 / size==cap,不浪费空间但多一个变量要同步维护。

两种都对,面试说清楚你选哪种、以及为什么就行。

四、为什么值得用循环队列

  • 定长、高效:数组连续内存、缓存友好,入队出队都是 O(1)。
  • 常见于底层:环形缓冲区(ring buffer)广泛用于 IO 缓冲、日志、生产者-消费者、网络收发缓冲、音视频流——都是「固定容量、循环复用」的场景。
  • 相比链表队列,省去了频繁分配/回收节点的开销。

五、对比链表实现的队列

链表队列天然不需要处理假溢出(动态分配),但每个节点有指针开销、缓存不友好、频繁 new 节点。循环队列用固定数组,容量上限确定的场景下更快更省。两者按「容量是否固定、是否在意内存/缓存」来选。

六、常见误区与追问

设计方式判空条件判满条件特点
浪费一个槽位front == rear(rear + 1) % capacity == front逻辑简单,实际容量少 1
维护 sizesize == 0size == capacity容量可用满,多维护一个变量
维护标志位看标志看标志容易写错,面试较少推荐

易错点:循环队列的数组下标会回绕,但元素顺序没有乱;真正的队头和队尾由 front/rear 语义决定,不由物理下标大小决定。

例如数组容量为 5,采用浪费一个槽位的设计,最多只能放 4 个元素。若 front=3,rear=2,队列并不是空,而是已经发生回绕;此时队列里的逻辑区间可能是 3,4,0,1。判断满要看 (rear+1)%5 == front,而不是看 rear > front

  • 误区:rear 小于 front 就说明队列错了。 循环队列允许下标回绕,rear < front 只是说明尾部已经绕到数组前面。
  • 误区:数组队列只要 rear 到末尾就满了。 如果前面有出队留下的空位,普通线性队列会假溢出,循环队列可以复用这些位置。
  • 误区:判空和判满都用 front == rear 这样无法区分空和满,所以要浪费一个槽位、维护 size 或维护额外标志。
  • 追问:为什么循环队列适合固定容量缓冲区? 它用固定数组复用空间,不需要频繁移动元素或分配节点。
  • 追问:入队和出队为什么是 O(1)? 只改一个位置和一个指针,取模回绕不依赖元素数量。
  • 追问:链表队列和循环队列怎么选? 容量固定且追求缓存友好时用循环数组;容量不确定且不想预分配时可用链表。

七、加强记忆

假溢出 = 线性数组队列出队后前面空间作废;循环队列用指针 +1 取模回绕复用空间。判空判满要打破 front==rear 的二义性:常用留一格空位(空 front==rear、满 (rear+1)%cap==front)或额外记 size。适合环形缓冲等定长场景。