← 中文资源

简体中文版本

双端队列的实现

用可扩容环形缓冲区实现双端队列,两端插入与删除均为摊还常数时间,并支持快照。

阅读英文原文

功能说明

Deque 支持在头部与尾部插入、删除和查看元素,push 与 pop 都是摊还 O(1),适合任务调度、回退历史、滑动窗口和广度优先搜索。内部使用环形缓冲区,避免数组 unshift 带来的整体搬移。toArray 按从前到后的顺序返回快照,size 暴露当前元素数量,空队列的 pop 返回 undefined。

关键代码

缓冲区初始容量为八,head 指向队首,size 记录有效元素数,尾部位置由 head 加 size 对容量取模得到。pushFront 先把 head 向前回绕一格再写入;popBack 先减少 size 再计算原尾部下标。扩容时按双倍容量创建新数组,从旧 head 开始逐项复制,并把 head 归零。删除位置会写入 undefined,帮助旧引用被回收。

使用注意

容量只增不减,长时间运行且元素数量波动大时可能占用较多内存,需要收缩策略时应自行实现重建。pop 返回 undefined 无法区分空队列与真实 undefined 元素,类型敏感场景可先检查 size。环形缓冲区的索引运算对容量有依赖,不要在外层直接修改内部数组。该实现不是线程安全的,Worker 之间需要消息传递。

验证方式

测试两端交替 push 与 pop,断言顺序符合后进先出或先进先出。构造跨越缓冲区边界的操作序列,验证回绕索引正确。持续 push 触发多次扩容,确认 toArray 顺序不变。测试空队列的 peek 与 pop、size 变化、单元素以及大量元素。最后检查 push 返回实例本身,且 toArray 的修改不会影响内部状态。

代码示例

// Double-ended queue backed by a growable ring buffer.
export class Deque {
  #buffer = new Array(8);
  #head = 0;
  #size = 0;

  pushBack(value) {
    this.#ensure(this.#size + 1);
    this.#buffer[(this.#head + this.#size) % this.#buffer.length] = value;
    this.#size += 1;
    return this;
  }

  pushFront(value) {
    this.#ensure(this.#size + 1);
    this.#head = (this.#head - 1 + this.#buffer.length) % this.#buffer.length;
    this.#buffer[this.#head] = value;
    this.#size += 1;
    return this;
  }

  popFront() {
    if (this.#size === 0) return undefined;
    const value = this.#buffer[this.#head];
    this.#buffer[this.#head] = undefined;
    this.#head = (this.#head + 1) % this.#buffer.length;
    this.#size -= 1;
    return value;
  }

  popBack() {
    if (this.#size === 0) return undefined;
    this.#size -= 1;
    const index = (this.#head + this.#size) % this.#buffer.length;
    const value = this.#buffer[index];
    this.#buffer[index] = undefined;
    return value;
  }

  peekFront() {
    return this.#size > 0 ? this.#buffer[this.#head] : undefined;
  }

  peekBack() {
    return this.#size > 0
      ? this.#buffer[(this.#head + this.#size - 1) % this.#buffer.length]
      : undefined;
  }

  get size() {
    return this.#size;
  }

  toArray() {
    const result = [];
    for (let index = 0; index < this.#size; index += 1) {
      result.push(this.#buffer[(this.#head + index) % this.#buffer.length]);
    }
    return result;
  }

  #ensure(capacity) {
    if (capacity <= this.#buffer.length) return;
    const next = new Array(this.#buffer.length * 2);
    for (let index = 0; index < this.#size; index += 1) {
      next[index] = this.#buffer[(this.#head + index) % this.#buffer.length];
    }
    this.#buffer = next;
    this.#head = 0;
  }
}