用数组模拟栈,队列,单调栈,单调队列

一、栈

栈:一种先进后出的存储结构。举个例子:吃薯片时在罐子里的薯片,最后一片薯片一定是最后装进去的那个,,当去吃的时候,一定先吃的是最上面的那个。栈也一样,最后插入的元素一定是最先取出

五种操作:

  1. 数组模拟创建栈(初始化)

     int stk[1000], tt = 0; //stk[]用来存放栈里的值,tt为栈顶坐标	
    
  2. 栈顶插入一个数x

     	 int x;
         cin >> x;
         stk[++ tt] = x;     //这里的有效数据从数组下标1开始存储 
    
  3. 栈顶(最后插入的数)弹出一个数

     tt --;   //由于栈后进先出的特点,弹出元素只需让栈顶下标后退
    
  4. 判断栈顶是否为空

     //如果栈的有效数据从下标为1开始,因此tt > 0栈就不为空
     if(tt > 0)  cout << "不空" << endl;  
     else cout << "空" << endl;
    
  5. 查询栈顶元素

     //直接输出栈顶元素即可
     cout << stk[tt] << endl;
    

二、队列

队列:字面意思,队列就像人排队,先排队的先办完事,先走人。队头弹出,队尾插入。

五种操作:

  1. 创建初始化队列

     //整个队列由两个指针hh和tt所维护
     int q[1010], hh  = 0, tt = -1;  //q[]存放有效数据,hh是队头,tt是队尾
    
  2. 队尾插入一个数x

      int x;
      cin >> x;
      q[++ tt] = x;   //有效数据从下标0开始存
    
  3. 队头弹出一个元素

     hh ++;  //弹出队头只需让指针hh右移即可
    
  4. 判断队列是否为空

     	//如果只插入了一个数tt为0,所以当hh<=tt时,队列就不为空
      if(hh <= tt)    cout << "不空" << endl;   
      else    cout << "空" << endl;
    
  5. 查询队头

     cout << q[hh] << endl;
    

三、单调栈

应用场景:给定一个序列,求它的左侧或右侧离他最近的比它大或小的第一个值

单调栈:依然是后进先出, 只不过栈内的值是单调的(从小到大or从大到小)

此处通过一道例题来了解单调栈

例子:acwing 830

给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。

输入格式
第一行包含整数 N,表示数列长度。

第二行包含 N 个整数,表示整数数列。

输出格式
共一行,包含 N 个整数,其中第 i 个数表示第 i 个数的左边第一个比它小的数,如果不存在则输出 −1。

数据范围
1≤N≤105
1≤数列中元素≤109

输入样例:

5
3 4 2 7 5

输出样例:

-1 3 -1 2 2

整体思路:对每个插入的数一个一个的处理,如果栈内还有有效数据并且该有效元素>= 待测试数据,就一直删除栈顶。停止删除后如果栈内还有有效数据就是答案,没有有效数据答案就是-1

时间复杂度:O(n)
具体代码如下:

#include 
using namespace std;
const int N = 1e5 + 10;
int stk[N], tt; //初始化栈,tt表示栈顶

int main()
{
    int n; 
    cin >> n;
    //对于插入的每个数,一个一个的处理
    while(n--)
    {
        int x; 
        cin >> x;   //测试每个数据
        //如果tt不为空(就是栈中已有有效数据)并且栈顶的数据>=x(将要插入栈中的数据),
        // 那么栈顶一定不是答案,所以将栈顶删掉。
        // 1.直到栈顶元素的值<待判断的值,那么栈顶元素就是答案。2.或是把栈中的元素删完,那么就表示栈中没有元素符合要求,输出-1
        while(tt && stk[tt] >= x)   tt--;   
        if(tt)  cout << stk[tt] << ' ';     //条件是栈不为空且栈顶元素<待测数据x
        else    cout << "-1" << ' ';
        
        //处理完每个待判断数据,将该数据插入栈中,为下个待判断元素提供答案(该元素可能是下组数据的答案)
        stk[++ tt] = x; 
    }
    return 0;
}

思考:
Q:前面的元素删完了,是否会影响后面某一元素的答案?

A:不会, 每处理一次数据,>= x的栈顶元素虽然会被删掉,
但是取而代之的是新插入(右侧)的更小的数,所以在新栈顶以前的数就不可能是答案, 因此存到最后该栈内的数据是单调递增的

四、单调队列

参考学习视频:单调队列经典问题:滑动窗口的最大值(讲解很详细)

大致思路:

用数组模拟栈,队列,单调栈,单调队列_第1张图片

数组a[]存全部数值,q[]存整个序列的单调子序列的下标值
每插入单调队列一个值,就判断窗口长度是否超出,超出就队头出队
然后判断队尾是否满足要求,若满足要求,直接插入。若不满足要求那么调整之前的队尾直至符合要求
然后将新元素插入队尾
最后判断如果窗口长度符合要求就输出

模拟过程:

用数组模拟栈,队列,单调栈,单调队列_第2张图片

一个例子:acwing 154

用数组模拟栈,队列,单调栈,单调队列_第3张图片

具体代码及分析注释如下:

#include 
using namespace std;
const int N = 1e6 + 10;

int a[N], q[N];     //a[N]存储该序列所有值,,q[N]存储该序列的子序列的下标值,并使其单调
//用q[[]存放队列的下标是为了方便判断队首何时出队,及滑动窗口的长度何时满足条件
//队头存放答案的下标
int main()
{
    int n, k;
    cin >> n >> k;
    for(int i = 0; i < n; ++i)  scanf("%d", &a[i]);
    
    int hh = 0, tt = -1;
    //滑动窗口最小值
    for(int i = 0; i < n; ++i)
    {
        //如果队列不为空,且队头下标 < 当前窗口的最左侧的下标,代表队头该出队了
        if(hh <= tt && q[hh] < i - k + 1)   hh ++;
        //队头出队后,判断队尾元素是否大于等于新插入元素,如果是,那说明队尾元素肯定不是答案,队尾元素出队
        while(hh <= tt && a[q[tt]] >= a[i])     tt--;
        //若不符合条件的队尾已经出队,或是新插入的元素满足要求(新插入的元素大于队尾元素),那么新插入的元素就有可能成为下一个滑动窗口的答案
        q[++ tt] = i;
        //最后判断是否满足长度为k的滑动窗口长度,满足就输出答案
        if(i + 1 >= k ) cout << a[q[hh]] << ' ';
    }
    cout << endl;
    -
    //输出每个滑动窗口的最大值
    hh = 0; tt = -1;
    for(int i = 0; i < n; ++i)  
    {
        if(hh <= tt && i - k + 1 > q[hh])   hh ++;
        while(hh <= tt && a[i] >= a[q[tt]])     tt--;
        q[++ tt] = i;
        if(i + 1 >= k)  cout << a[q[hh]] << ' ';     
    }
    return 0;
}

你可能感兴趣的:(算法,数据结构,算法,c++)