空队: F=R=0 哨兵=0
入队1次: F=0 R=1 哨兵=1
入队2次: F=0 R=2 哨兵=2
……
入队7次: F=0 R=7 哨兵=7
再入队: R 追到 F 前 判满,停止
F=0位置不写值
对,F=0 那一格在“只从尾部入队”的全程里始终不写值——它就干两件事:
1. 空队时当哨兵(F=R=0)。
2. 队列启动后当永久队头指针,但元素存在它后面那一圈,它自己永远空着,直到你从头部出队把 front 滚走,或者从头部入队把 front 往前挪一格再写。
所以只要一直 enqueue_tail 下去,front 就钉在 0 不动,那一格永远空,永远不写值。
• 空:front == rear
• 满:(rear+1)%MAX == front
这两句话其实是在描述同一个问题的两种不同状态,核心都是在说“明明有空位却不能用”。
我来用大白话和具体的例子帮你拆解一下这两句话到底是什么意思:
🤔 1. 第一句话:“为了避免普通数组队列出现的假溢出”
● “假”在哪里?
● 真溢出:是指队列真的装满了,一点空地方都没有了。
● 假溢出:是指程序“以为”队列满了,报错说不能加人了,但实际上队列里还有很多空座位。
● 为什么会“假”?
● 因为普通的数组队列(非循环),指针只能往后移,不能往前挪。
● 当你删除队头元素后,前面腾出了空间,但队尾指针( rear )已经跑到了数组最后面。程序一看 rear 到底了,就判定“满了”,哪怕前面刚腾出了好几个位置。
🧩 2. 第二句话:“数组前面空着,后面却满了无法插入”
这句话是“假溢出”的具体画面。为了让你看懂,我们来看一个具体的例子:
假设你有一个长度为 5 的普通数组队列(不是循环的)。
第一步:装满数据
你依次放入了 A, B, C, D, E。
此时队列是满的, rear 指针指向了数组的第5个位置(索引4)。
数组下标:
元素: A B C D E
指针: front rear (指向末尾)
第三步:尝试入队(插入)
这时候,你想新来一个人 F 要排队。
● 实际情况:数组里明明还有两个空位(前面),应该能装下 F。
● 程序判断:程序一看队尾指针 rear ,发现它已经指向了数组边界(索引4),再往后就要越界了!于是程序大喊:“队列满了!插不进去了!”(这就是假溢出)。
所以用循环顺序队列
循环队列就是为了解决这个问题而生的。它把数组想象成一个圆圈,当 rear 指针走到末尾时,如果发现前面有空位,它会自动“绕”回到数组开头去利用那些空位,从而避免了这种尴尬的“假溢出”。
● 顺序队列的痛点(假溢出):
顺序队列使用数组实现,内存是连续的。当队头元素出队后,该位置虽然空闲了,但队尾指针已经指向了数组边界,无法复用前面的空闲空间。即使数组前面有空位,后面满了也会报“溢出”,这被称为“假溢出”。虽然循环队列可以解决这个问题,但逻辑变得复杂。
● 链队列的解法(动态扩展):
链队列使用链表,节点在内存中是离散分布的。每当有新元素入队,程序就动态申请一块内存( malloc 或 new )创建节点;元素出队时,就释放该节点的内存。
● 只要系统还有可用内存,链队列就可以一直申请空间来存储新数据,不会因为“物理位置不连续”而受限。
● 它不存在“假溢出”问题,只有在系统物理内存真正耗尽时才会无法插入。