队列

简介 #

队列是一种「先进先出」的数据结构.元素从队列的前端进入(入队),从末端离开(出队),类似于排队.基本操作见 STL Queue.

双向队列 #

队列元素只能从一端进,另一端出,有时无法满足问题的需要.双向队列应运而生,它支持从两端插入或删除元素.

双向队列的基本操作见 STL Deque.

单调队列 #

单调队列的元素从队头到队尾满足单调性,适用于查询某一动态区间的最大(或最小)元素.

插入元素 #

将 $A[i]$ 入队,维护队列单调性,同时保证队列元素在 $A[p\cdots i]$ 范围内.以单调递增队列为例:

  • 重复弹出队头,直到队头 $≥p$;

  • 重复弹出队尾,直到 $A[$队尾$]<A[i]$(若单调递减,则重复直到 $A[$队尾$]>A[i]$).

  • 将 $i$ 入队.

涉及双端操作,须使用双向队列.此时 $A[p\cdots i]$ 范围内最小元素为 $A[$队头$]$.

deque<int> q; // 存储元素下标

void insert(int i, int p) { // 将 a[i] 入队,维护队列元素在 a[p...i] 范围内
    while(!q.empty() && q.front() < p) q.pop_front();
    while(!q.empty() && a[q.back()] >= a[i]) q.pop_back();
    q.push_back(i);
}

滑动窗口 #

一个滑动窗口(长度为 $k$)从数组 $A$ (长度为 $n$)的左端移动到右端,每次只向右移一位.求每次滑动时窗口区中的最大值.

示例($k=3,n=8$,红色数值在窗口区内):

$A_1$ $A_2$ $A_3$ $A_4$ $A_5$ $A_6$ $A_7$ $A_8$ 最大值
1 3 -1 -3 5 3 6 7 3
1 3 -1 -3 5 3 6 7 3
1 3 -1 -3 5 3 6 7 5
1 3 -1 -3 5 3 6 7 5
1 3 -1 -3 5 3 6 7 6
1 3 -1 -3 5 3 6 7 7

朴素算法 #

枚举 $i=k\rightarrow n$,枚举出区间 $[i-k+1,i]$ 中的最大整数.时间复杂度为 $O(nk)$.

for(int i = k; i <= n; i ++) {
    int maxn = a[i];
    for(int j = i - k + 1; j <= i; j ++)
        maxn = max(maxn, a[j]);
    cout << maxn << ' ';
}

单调队列优化 #

使用单调递减队列优化「查找区间 $[i-k+1,i]$ 中的最大整数」的效率.时间复杂度为 $O(n)$.

deque<int> q;

void insert(int i, int p) {
    while(!q.empty() && q.front() < p) q.pop_front();
    while(!q.empty() && a[q.back()] <= a[i]) q.pop_back();
    q.push_back(i);
}

for(int i = 1; i <= n; i ++) {
    insert(i, i - k + 1);
    if(i >= k) cout << a[q.front()] << ' ';
}