一.栈的基本概念
栈是一种特殊的表,这种表只在表首进行插入和删除操作。
因此,表首对于栈来说具有特殊的意义,称为栈顶。相应的,表尾称为栈底。不含任何元素的栈称为空栈。
栈的修改遵循后进先出的原则,Last In First Out,可以想象成一个水桶。
二.常用的栈运算
栈也是一个抽象数据类型。常用的栈运算如下:
(1)StackEmpty(S):测试栈S是否为空
(2)StackFull(S):测试栈S是否以满
(3)StackTop(S):返回栈S的栈顶元素
(4)Push(x,S):在栈S的栈顶插入元素x,简称为将元素x入栈
(5)Pop(S):删除并返回栈S的栈顶元素,简称为抛栈
三.栈的几种实现
1.用数组实现栈
实现与表类似,只不过要实现的运算不同。略
2.用指针实现栈
用这种方法实现的栈也叫做链栈。
因为栈是一种特殊的表,所以实现与表类似,只不过要实现的运算不同。
四.栈的应用举例
例:在对用高级语言编写的程序进行编译时会遇到表达式或字符串的括号匹配问题。
在从左到右逐个字符对给定的表达式expr进行扫描的过程中,将所遇到的左括号存入一个栈中。
每当扫描到一个有括号时,如果栈非空,就将其与栈顶的左括号相匹配,如果匹配,从栈顶删除该左括号,如果不匹配,栈中的左括号不匹配;
若栈已空,则所遇到的右括号不匹配。
在完成对表达式的扫描后,若栈非空,则留在栈中的左括号不匹配。
因为这个操作频繁在尾部增加和删除元素,所以适合用栈。
五.单调栈
应用:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。
单调栈的本质是空间换时间,就是用一个栈来记录我们遍历过的元素,简单说就是模拟人的记忆力。
首先,什么是单调栈呢?单调栈是一种特殊的数据结构,它只支持两种操作:入栈和出栈。但是与普通的栈不同的是,单调栈中存储的元素具有单调性,单调递增或单调递减。因此单调栈分为单调递增栈和单调递减栈。
那么如何维护栈的单调性呢?以单调递增栈为例,当我们要将一个新元素入栈时,如果新元素比栈顶元素小,那么栈顶元素就无法继续保持递增关系,因此需要将栈顶元素弹出。直到新元素可以入栈为止,这样就能保证栈内元素的单调递增关系。
例1:下面是例子和动图:
含动图,解决单调栈问题_单调栈算法gif图演示-CSDN博客
例2:给定一个数组arr,对于每个元素arr[i],需要找到它左边第一个比它小的元素。如果没有,就用-1代替。使用单调栈可以在O(n)的时间复杂度内完成这个任务。
用c++实现:
vector<int> prevSmallerElement(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, -1); // 初始化结果数组,所有元素初始值为-1
stack<int> st; // 创建一个栈用于维护单调递增关系
for(int i=0;i<n;++i) { // 遍历数组nums
while(!st.empty() && nums[i] <= nums[st.top()]) {
// 当栈不为空且当前元素小于等于栈顶元素时,循环弹出栈顶元素
st.pop();
}
if(!st.empty()) {
// 如果栈不为空,说明当前元素的左边第一个比它小的元素是栈顶元素
res[i] = nums[st.top()];
}
st.push(i); // 将当前元素索引入栈,维护单调递增关系
}
return res; // 返回结果数组
}
例3:数据结构 栈 3.23 郊游
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
int main() {
int n ;
cin>>n;
vector<int> result(n, 0); // 初始化结果数组,所有元素初始值为-1
//单调栈,就相当于记忆力
stack<int> left;
stack<int> right;
//输入山的高度
int mountain[n];
for(int i=0; i<n; i++) {
cin>>mountain[i];
}
//从左侧遍历山,计算左边
for(int i=0; i<n; i++) {
//维护单调栈(记住看到的单调递增的地方),
//使得进去新的元素栈仍然单调
//如果是空的,直接进入
if(left.empty()) {
//什么都看不到,直接把元素加入栈
left.push(mountain[i]);
}else{
//如果不是空的,如果新进入的元素仍符合单调递减,就进入,否则头元素出去
//重复这个过程,除非栈为空
while(!left.empty()&&left.top()<=mountain[i]){
left.pop();
}
//此时栈的元素就是左边能看到的山的数量
result[i]=left.size()+result[i];
//把元素加入栈
left.push(mountain[i]);
}
}
//从右侧遍历山,计算右边
for(int i=n-1; i>=0; i--) {
//维护单调栈(记住看到的单调递减的地方),
//使得进去新的元素栈仍然单调
//如果是空的,直接进入
if(right.empty()) {
//什么都看不到,直接把元素加入栈
right.push(mountain[i]);
}else{
//如果不是空的,如果新进入的元素仍符合单调递减,就进入,否则头元素出去
//重复这个过程,除非栈为空
while(!right.empty()&&right.top()<=mountain[i]){
right.pop();
}
//此时栈的元素就是左边能看到的山的数量
result[i]=right.size()+result[i];
//把元素加入栈
right.push(mountain[i]);
}
}
for(int i=0;i<n;i++){
//加1是因为本身也是风景
cout<<result[i]+1<<" ";
}
}