“考虑固定长度分组组成的FIFO。容量C以包计;q是本次新包加入之前尚在等待的包数,不包括已经交给下游的包。入队、出队按单调整数tick及同刻给定次序执行。每个包最终只能进入队列、被丢弃或已交…”
形式陈述
状态与操作
令抽象状态为有限序列
| 操作 | 返回值 | 新状态 |
|---|---|---|
enqueue(x) |
无 | |
front(),要求 |
原状态 | |
dequeue(),要求 |
||
isEmpty() |
原状态 |
从空队列依次加入 A、B,取出一次,再加入 C,状态由 [A,B] 变成 [B],再变成 [B,C]。下一次必须取出 B,因为 B 比 C 更早加入,且还没有离队。
空队列上的 front、dequeue 需要明确失败行为。有界队列还要说明满队列时是拒绝、等待还是覆盖旧项;这三种策略具有不同的可观察语义。尤其是“覆盖最旧元素”的环形日志,不能在没有声明的情况下充当不丢元素的普通队列。
网络FIFO可在硬容量之外另做主动拥塞通知。RED在到达时更新平均队长,以条件概率提前选择丢弃或标记;CoDel在出队时观察驻留时间,并用移除候选后的剩余字节保护短队列。二者仍须交代每个包究竟入队、丢弃还是交付;提前通知不取消本页的满队列合同。
直觉
让最早等待的元素先离开
队列(queue)按照先进先出(FIFO)的规则处理元素:从队尾加入,从队首取出。可以把它想成一列有编号的待处理任务;后来加入的任务排在后面,不能越过仍在等待的旧任务。
这是一个抽象数据类型的行为规则,而不是特定内存形状。链表可以连接队首到队尾,数组也可以借助循环下标复用槽位。与栈相比,差别在于从哪一端删除,而不在于有没有使用指针。[1]
例子与边界
数组不必每次把元素往前搬
若总让队首占据数组下标
取容量
非满时,入队写入
容量为 — 表示不属于逻辑队列的槽位:
| 操作完成后 | 下标 0 | 下标 1 | 下标 2 | 下标 3 | 逻辑队列 | ||
|---|---|---|---|---|---|---|---|
| 加入 A、B、C | A | B | C | — | 0 | 3 | A、B、C |
| 取出 A、B | — | — | C | — | 2 | 1 | C |
| 加入 D、E | E | — | C | D | 2 | 3 | C、D、E |
| 加入 F | E | F | C | D | 2 | 4 | C、D、E、F |
最后一行数组中的物理次序是 E,F,C,D,逻辑次序却仍是 C,D,E,F。循环下标让两者对应,不要求它们看起来一致。
若只存首尾两个模
三种常见实现的时间界
固定容量循环数组在非满、非空等相应条件下,可以用最坏
链表实现同时保存头、尾引用。入队接到尾部,出队删除头部,都只修改常数个链接。删除最后一个节点时必须同时把头、尾置空,否则尾引用会指向已经离队的节点。在常数大小节点、局部分配释放按单位成本的模型下,这些操作为最坏
队列还可以用两个栈实现:新元素压入输入栈;需要出队且输出栈为空时,把输入栈逐个弹出并压入输出栈,反转后的最旧元素恰好位于输出栈顶。每个元素至多经历一次这样的转移,所以从空结构开始的一串操作总成本为线性,单次出队却可能搬动很多元素。这是摊还常数时间,不是最坏常数时间。
推论与应用
FIFO 在算法里提供了什么
广度优先搜索从起点入队开始,每次取出队首,再把尚未发现的邻居加入队尾。只在第一次发现时入队,队列就按距离层次推进:距离为
“访问过”的标记通常在入队时设置。若等到出队才标记,多个前驱可能把同一个顶点重复加入,破坏每个顶点只入队一次的时间记账。
任务系统中的FIFO要明确约束的是哪个事件。按FIFO分发给多个工作线程,只保证取任务的次序,不保证任务完成次序;较晚取出的短任务可能先结束。并发实现如Michael–Scott队列进一步用链接CAS与Head推进定义入队/出队的生效时刻;Tail暂时落后可由别人帮助修复,并不改变抽象FIFO内容。
亏额轮转分组调度同时使用每流FIFO和活跃流FIFO:一次服务只扣本流头包,未用字节预算保留到下轮,非空流再加入活跃队尾。这样可以在包长不等时讨论字节份额,而不是把“每流一次”误当相等的已发送字节数。
优先队列按优先级选择下一项,双端队列允许两端操作,二者提供的都不是纯FIFO接口。
循环缓冲区也可结合动态数组的扩容策略,但扩容时必须按逻辑队列顺序搬移元素。
参考资料
[1] Robert Sedgewick、Kevin Wayne,Algorithms, 4th ed., Addison-Wesley, 2011,§1.3: Bags, Queues, and Stacks:队列接口与带头尾引用的链式实现。
[2] Pat Morin,Open Data Structures: An Introduction,Athabasca University Press,2013,§2.3 ArrayQueue 与第 3 章:循环下标、容量管理及摊还成本。