Skip to content

双端队列

为什么需要双端队列

栈只能在一端操作,队列只能一端入、另一端出——两者的限制都比较强。在某些场景下,我们希望两端都能灵活地插入和删除,这就产生了双端队列(Deque, Double-Ended Queue)。

双端队列是栈和队列的推广形式。408 考研中,双端队列本身的代码实现考得少,但输出序列的判断是选择题的高频考点。

核心思想

双端队列的核心特点:

  • 两端均可操作:前端(front)和后端(rear)都允许插入和删除
  • 栈和队列都是它的特例:若限制只从一端插入和删除,就退化为栈;若限制一端只插入、另一端只删除,就退化为队列
  • 受限双端队列:对某一端的操作加以限制,可得到输入受限双端队列和输出受限双端队列

双端队列的逻辑结构:

  前端 front                      后端 rear
    ↕                               ↕
 ┌──────┬──────┬──────┬──────┬──────┐
 │  a₁  │  a₂  │  a₃  │  a₄  │  a₅  │
 └──────┴──────┴──────┴──────┴──────┘
  可插入/删除                    可插入/删除

三种双端队列的对比:

类型前端后端
双端队列可插入、可删除可插入、可删除
输入受限双端队列只能删除可插入、可删除
输出受限双端队列可插入、可删除只能删除

注意:输入受限是限制"输入"只能从一端进行,两端都能输出;输出受限是限制"输出"只能从一端进行,两端都能输入。

输入受限 vs 输出受限双端队列对比

关键区别

  • 输入受限:输入只能从一端进行,输出可以从两端进行
  • 输出受限:输出只能从一端进行,输入可以从两端进行
  • 两者能产生的合法输出序列都是栈的合法序列的超集

交互可视化

通过下方的交互动画,你可以逐步观察双端队列的执行过程:

加载可视化中...

操作详解

输入受限双端队列

输入受限双端队列:只允许从一端插入,但两端都可以删除

cpp
// 输入受限双端队列——只能从 rear 端插入,front 和 rear 端都可删除
#define MaxSize 50
typedef struct {
    int data[MaxSize];
    int front, rear;  // front 指向队头,rear 指向队尾的下一个位置
} InputRestrictedDeque;

// 从 rear 端插入(唯一的插入方式)
bool InsertRear(InputRestrictedDeque &dq, int x) {
    if ((dq.rear + 1) % MaxSize == dq.front) return false; // 队满
    dq.data[dq.rear] = x;
    dq.rear = (dq.rear + 1) % MaxSize;
    return true;
}

// 从 front 端删除
bool DeleteFront(InputRestrictedDeque &dq, int &x) {
    if (dq.front == dq.rear) return false; // 队空
    x = dq.data[dq.front];
    dq.front = (dq.front + 1) % MaxSize;
    return true;
}

// 从 rear 端删除
bool DeleteRear(InputRestrictedDeque &dq, int &x) {
    if (dq.front == dq.rear) return false; // 队空
    dq.rear = (dq.rear - 1 + MaxSize) % MaxSize;
    x = dq.data[dq.rear];
    return true;
}

关键理解:输入受限 ≠ 栈也 ≠ 队列。因为删除可以在两端进行,所以它能产生的输出序列比栈和队列都多,但比一般双端队列少。

输出受限双端队列

输出受限双端队列:两端都可以插入,但只允许从一端删除

cpp
// 输出受限双端队列——front 和 rear 端都可插入,只能从 front 端删除
typedef struct {
    int data[MaxSize];
    int front, rear;
} OutputRestrictedDeque;

// 从 rear 端插入
bool InsertRear(OutputRestrictedDeque &dq, int x) {
    if ((dq.rear + 1) % MaxSize == dq.front) return false;
    dq.data[dq.rear] = x;
    dq.rear = (dq.rear + 1) % MaxSize;
    return true;
}

// 从 front 端插入
bool InsertFront(OutputRestrictedDeque &dq, int x) {
    if ((dq.rear + 1) % MaxSize == dq.front) return false;
    dq.front = (dq.front - 1 + MaxSize) % MaxSize;
    dq.data[dq.front] = x;
    return true;
}

// 从 front 端删除(唯一的删除方式)
bool DeleteFront(OutputRestrictedDeque &dq, int &x) {
    if (dq.front == dq.rear) return false;
    x = dq.data[dq.front];
    dq.front = (dq.front + 1) % MaxSize;
    return true;
}

关键理解:输出受限 ≠ 栈也 ≠ 队列。因为插入可以在两端进行,元素可以"插队"到前端,所以它能产生比栈更多的输出序列。

复杂度分析

操作时间复杂度说明
前端插入O(1)修改 front 指针
后端插入O(1)修改 rear 指针
前端删除O(1)修改 front 指针
后端删除O(1)修改 rear 指针

空间复杂度:O(n),需要存储 n 个元素。所有操作只需常数级辅助空间。

考研高频考点

  • 输出序列判断(选择题最高频):给定输入序列 1, 2, 3, 4,判断某个输出序列能否由输入受限/输出受限双端队列产生。解题方法——逐个元素模拟,检查每次操作是否符合受限规则
  • 与栈的输出序列对比:栈合法的输出序列,输入受限和输出受限双端队列一定也合法;但反过来不成立。即:栈的合法序列 ⊂ 受限双端队列的合法序列 ⊂ 一般双端队列的合法序列
  • 概念辨析:区分输入受限和输出受限的定义(哪一端受限、受限的是插入还是删除)
  • 双端队列与栈、队列的包含关系(栈和队列都是双端队列的特例)
  • 判断一个序列"既不能由输入受限产生,也不能由输出受限产生"的情况(较少见但偶尔考)