栈和队列

发布时间 2023-12-11 13:14:39作者: shimingxin1007

前言

这里我们主要介绍手写栈和队列。

虽然有 \text{STL} 里的分装好的数据结构,但是因为封装好的数据结构跑得会很比较慢(比如 \text{deque} ),所以我们最好手写。

正文

普通栈

栈是一种后入先出的数据结构,它主要有三种功能:

  • 往栈里加入一个元素
  • 从栈头弹出一个元素
  • 查询栈顶端的元素。

所谓后入先出,意思是当执行 2 操作时,会优先弹出最近一个加入栈中的元素。

我们可以使用结构体手写栈,我们可以如下定义:

struct stack{
	int sta[100020];
	int top=0;
};
stack<int> s;

其中, sta[] 表示栈, top 表示长度。

以上三种操作,我们便可以通过三个成员函数进行手写:

 void push(int data){//操作 1
        sta[++top]=data;
    }//成员函数
 void pop(){//操作 2
        top--;
    }
 int gettop(){//操作 3
        return sta[top];
    }

再使用结构体将其封装,就可以实现手写栈多个栈了。

但是,以上代码仅支持栈元素是单个类型的,如果要定义 doublestringchar……类型的栈,是不是就太麻烦了。

这时候,我们便可以定义 template<typename T>,将类型设为 T ,进行如下定义方法:

template<typename T>
struct stack{
    T sta[100020];
    int top;
    void push(T data){
        sta[++top]=data;
    }
    void pop(){
        top--;
    }
    T gettop(){
        return sta[top];
    }
}
stack<int> a;
stack<double> b;
stack<string> c;
stack<char> d;
......

单调栈

例题导入

【模板】单调栈

很明显,暴力 \mathcal{O(n^2)} 。

考虑二分,时间复杂度 \mathcal{O(nlogn)} 。

能不能更快呢?

尝试用栈来加速这个求解过程。

假设在第 4 个位置是 5 ,在第 5 个位置有是 3 ,那么,对于位置 (1,3) 而言,位置 5 永远不是我们想求的答案。

于是我们会想,哪些数可能作为我们的答案,我们发现,可能成为我们答案的数应该是一个满足位置和高度均具有单调性的序列。

所以我们可以从右向左扫描序列,维护一个可能可以成为答案的序列。对于当前扫描到的数,我们把不可能成为答案的数从这个序列中剔除。这样,由于位置具有单调性,答案序列中位置最靠前的,也就是我们当前这个数对应的答案。最后,我们把当前的数加进可能成为答案的序列之中。

模拟以上操作即可:

/*Written by smx*/
#include<bits/stdc++.h>
using namespace std;
template<typename T>
struct stack_{
	T sta[3000005];
	int top=0;
	void push(int data){
		sta[++top]=data;
	}
	void pop(){
		top--;
	}
	T gettop(){
		return sta[top];
	}
};
stack_<int> s;//维护下标具有单调性
int n,ans;
int h[3000005],f[3000005];
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	//ios::sync_with_stdio(false);
	//cin.tie(0);
	//cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>h[i];
	}
	s.push(0);
	for(int i=n;i>=1;i--){
		while(s.top&&h[i]>=h[s.gettop()]){//判断不会访问出界,且当前数大于栈内的数。注意用循环而不是if语句
			s.pop();
		}
		f[i]=s.gettop();//记录答案
		s.push(i);//新的数入队
	}
	for(int i=1;i<=n;i++){
		cout<<f[i]<<" ";
	}
	return 0;
}

普通队列

队列是一种先入先出的数据结构,先加进输出结构的元素会被先弹出。就像食堂
排队一样,先排好队的同学会先打到饭,这就是一种队列。

数组+2个指针

同理,定义如下:

template<typename T>
struct queue{
	T que[3000005];
	int head=1,tail=0;
	void push(int data){
		que[++tail] = data;
	}
	void pop(){
		head++;
	}
	int front(){
		return que[head];
	}
	int back(){
		return que[tail];
	}
	int get_size(){
		return tail-head+1;
	}
};
queue<int> s;

使用时, head 表示队首, tail 表示队尾。

循环队列

由于不断地进行 head++,数组会多出很多元素,浪费空间,我们便可以将这些空间利用起来,取模一个数,不过整场比赛一般不卡这个,即使会卡:

queue q:?????

人话:屁用没有

双端队列

类似普通的队列的定义,我们可以定义一个左右两端都可以入队列和出队列的数据结构,那么这个结构就是双端队列。