栈和队列
$$\texttt{栈}$$
栈(stack
),一种后进先出(last in first out,LIFO)的数据结构,主要有三种操作:压入一个元素到栈顶(push(x)
),弹出栈顶的元素(pop()
),访问栈顶元素(top()
),当然也有询问大小(size()
)和返回是否为空(empty()
)。
注:后进先出指的是在当前容器内后进来的先出。
最初的最初:实现一个基本的栈
由于三个操作都只与栈顶有关,所以可以用数组轻松实现。
int stk[N], top;
void Pop () { // 弹出栈顶
top--;
}
void Push (int x) { // 将元素压入栈
stk[++top] = x;
}
int Top () { // 返回栈顶元素
return stk[top];
}
int Size () { // 返回栈的大小
return top;
}
bool Empty () { // 如果栈为空返回 1,否则返回 0
return !top;
}
同时 C with STL(c++) 也附赠了一个 STL 容器 stack<元素类型> 容器名;
需要头文件 #include <stack>
。
它的成员函数如下:stack<int> stk;
- 元素访问
stk.top()
返回栈顶。
- 修改
stk.push(x)
将 \(x\) 压入栈顶。stk.pop()
弹出栈顶。
- 容量
stk.empty()
返回是否为空。stk.size()
返回元素数量。
废柴的进阶:双端栈
顾名思义,等同于双端队列,见下文。
一个有用的进阶:单调栈
单调栈,顾名思义,就是满足单调性的栈。
假设现在有一个存储整数的单调栈,满足从栈顶往下数都是单调递增的。
初始状态:\([0,11,45,81]\)。
如果要压入整数 \(14\),为了保证单调性,要将两个比 \(14\) 小的数弹出,再压入整数 \(14\),此时栈中元素为 \([14,45,81]\)。
const int N = 2e5 + 10;
int stk[N], top;
void Pop () { // 弹出栈顶
top--;
}
void Push (int x) { // 将元素压入栈
while (top && stk[top] <= x) {
Pop();
}
stk[++top] = x;
}
int Top () { // 返回栈顶元素
return stk[top];
}
int Size () { // 返回栈的大小
return top;
}
bool Empty () { // 如果栈为空返回 1,否则返回 0
return !top;
}
由于每个元素最多只会入栈出栈各一次,所以复杂度为 \(O(n)\) (\(n\) 为元素数量)。
习题
$$\texttt{队列}$$
队列(queue
),一种先进先出(first in first out,FIFO)的数据结构,它满足先入队列的元素一定先出队列(双端队列除外)。
最初的最初:实现一个基本的队列
主要是四种操作:压入一个元素到队尾(push(x)
),弹出队头的元素(pop()
),访问队头元素(front()
),访问队尾元素(back()
),当然也有询问大小(size()
)和返回是否为空(empty()
)。
一般不建议手写,比较容易出锅,比起来 STL 真是个好东西,STL 队列 queue<元素类型> 容器名;
。
需要头文件 #include <queue>
。
它的成员函数如下:queue<int> q;
- 元素访问
q.front()
返回队头元素。q.back()
返回队尾元素。
- 修改
q.push_back(x)
将 \(x\) 压入队尾。q.pop_front()
弹出队头。
- 容量
q.empty()
返回是否为空。q.size()
返回元素数量。
一个进阶:双端队列
比起普通的队列,双端队列将 \(2\) 种压入弹出升级为了 \(4\) 种!
STL 也提供了双端队列,头文件没变。
deque<元素类型> 容器名;
假设现在 deque<int> dq;
,那么四种压入弹出分别为:
dq.push_front(x)
将 \(x\) 压入队头。dq.push_back(x)
将 \(x\) 压入队尾。dq.pop_front()
弹出队头。dq.pop_back()
弹出队尾。
别的成员函数没变,值得一提的是它支持随机化访问!即你可以用 dq[0]
来访问队头。
不过常数比 queue
大,非必要情况下不建议使用。
另一个进阶:单调队列
先放一道例题:P1886 滑动窗口 /【模板】单调队列。
大致题意就是给定一个长度为 \(n\) 的整数数组 \(a\),要对于每个长度为 \(k\) 的区间求出区间的最大最小值。
从前往后处理,为了维持单调性,那么当你要压入元素时,你可以用类似单调栈的方式,将那些不优的元素先弹出,再压入元素。
不同的是,这里区间长度为 \(k\),那么“过期”元素则需要特殊处理。很明显单调队列中的元素下标也是单调递增的,直接重复弹出队头即可。
由于涉及到了队尾弹出,可以使用双端队列。
细节有些,但不多。