简介
单调栈(monotonic stack)是一种由栈(stack)衍生出来的数据结构,其操作方式与一般的栈基本一致,只是在元素入栈的时候,需要保证其与原先栈顶元素的大小关系1;若不满足,则不断将栈顶元素弹出,直至关系满足或栈为空为止。以上操作使得栈内的元素具有“单调性”。
但是,由于栈类数据结构通常只能在栈顶位置读写,我们也并不会直接利用容器内元素,而是在新元素入栈时,利用自然产生的信息,即哪些元素被按照规则弹出。也可以在一轮入栈操作后,“观察”栈内剩下的元素,得出一定的结论。
第一个实现
由于C++语言标准在头文件stack
中定义了表示一般栈的模板类,可以用它来作为基础。这个数据结构本身逻辑较为简单,不难写出如下单调递增栈的实现(即新入栈的元素必须大于原先栈顶的元素):
// with:
// #include <stack>
// using namespace std;
template <typename T>
class monotonic_stack : public stack<T>
{
public:
// these three do not change, using them to simplify
using stack<T>::empty;
using stack<T>::top;
using stack<T>::pop;
void push(T val)
{
while (!empty() && val <= top())
{
pop();
}
stack<T>::push(std::move(val)); // call push of base class
}
};
通用性改进
入栈时的额外操作
在简介中提到,这种数据结构的用途往往在于其push
操作中自然形成的结构。如果像上面的代码那样封装,那这一信息对外界是缺失的,就无法发挥这个数据结构真正的作用。弥补这一缺陷的方式有很多,例如可以把被自动弹出的元素整体返回,或者利用回调函数直接在发生自动弹出的时刻对这样的情况进行处理。整体返回往往要较多考虑内存管理和性能的问题,因此这里给出一个用模板参数封装回调的实现:
// with:
// #include <stack>
// using namespace std;
template <typename T, typename callback>
class monotonic_stack : public stack<T>
{
private:
callback _cb;
public:
// these three do not change, using them to simplify
using stack<T>::empty;
using stack<T>::top;
using stack<T>::pop;
void push(T val)
{
while (!empty() && val <= top())
{
_cb(top(