test2

冷暖自知 提交于 2019-12-01 10:22:49

栈是一种在竞赛中非常实用的基础数据结构。本篇笔记主要记录了栈的一些例题与技巧。

基本性质

对于一个栈,保证有以下性质

  • 先入栈的元素一定后出
  • 后入栈则反之

例如对于$3,6,0,1,2$这一组数据来说,假设我们将他们全部入栈,再全部出栈,那么出栈后的序列为$2,1,0,6,3.$

  • 【例题】$push,pop,Getmin$

    实现一个栈,支持入栈,出栈和查询最小值的工作,要求时间复杂度均为$O(1).$

$  $对于入栈、出栈的功能,$c++ STL stack$中自带的函数时间复杂度为$O(1)$。但是对于查询最小值而言,栈本身的性质并不支持这种操作。
$  $我们可以考虑在维护一个$stack$的同时,再维护一个二叉堆(即优先队列),这样就可以达到题目的要求。然而,这样的时间复杂度为$O(logN).$
$  $所以我们需要一种更高效的算法。我们发现,每一次入栈、出栈的时间复杂度均为$O(1)$;而我们又知道,假设有两个数$x,y$,当我们每对这两个数做出一次比较是,时间复杂度也是$O(1).$我们利用这样的性质,同时维护两个栈结构$P_1,P_2$,其中$P_1$存放的是原本的数据,而$P_2$存放的是从$P_1$栈底$P_{1_{0/1}}$至当前栈顶$P_{1_n}$的最大值。
$  $这样,我们就可以梳理出一个程序的脉络$.$

  • 若此次执行的是$push$操作,则将此元素$x$压进$P_1.$并将$x$与$P_{2_n}$比较,若$x$小于$P_{2_n}$,则将$x$压入$P_2.$
  • 若此次执行的是$pop$操作,则将当前$P_{1_n},P_{2_n}$全部弹出$.$
  • 若此次执行的是$Getmin$操作,则直接返回$P_{2_n}$即可$.$

部分代码:

if(s=="push")
{
P1.push(x);
if(x<P2.top())
P2.top(x);
}
else if(s=="Pop")
{
P1.pop();
P2.pop();
}
else
{
unsigned long long f=P2.top();
cout<<f<<endl;
}

$  $在上面的例题中,我们巧妙地利用了栈的性质,通过维护两个栈达到了目的。我们通过利用栈,还可以再很乐观的时间复杂度里做出一道题目,甚至可以与$dp$(动态规划)比拟!


又例如说$noip$一道经典的例题:“栈”

关于$noip,it's dead$

不!今天不讨论这个!

题意简述:

给出$n$个数字以及一个深度大于$n$的栈,每次可以执行入栈/出栈$2$种操作。问可以得到多少种不同的出栈序列$.$

$  $我们可以思考出一种很显然的暴力搜索:对于每次操作$d$我们只有两种选择

  1. 将当前的栈定元素出栈
  2. 将当前未经栈序列中的一个数进栈

$  $使用这样类似于$dfs$的思想,我们可以正确的做出这题。然而,这样需要枚举$n$个数,所以时间复杂度约为$O(2^n)$,我们肯定是不能接受的$.$

$  $所以我们考虑如何降低时间复杂度。上述算法的时间主要来源是枚举$n$个数的开销。我们可以想到题目只是让我们求出种数,而并不关心每种方案的内容。根据这种思想,我们可以发现一个很显然的不断求子问题的递推方案。假设方案总数为$f_n$。

$  $以$1$为例子来说,假设$1$放在第$j$位出栈,那么这个问题就被划分成了$2$个子问题:

  • 前$j-1$个数模拟进出栈的过程
  • 后$n-j$个数模拟进出栈的过程

然后按照这样的过程递推下去,可以得到递推式:

$$f_n=\sum\limits_{j=1}^nf_{j-1}*f_{n-j}$$

$  $到这里,递推的$O(n^2)$复杂度下就可以$\mathtt{AC}$了。但是本题还有一种数学方法,也就是$Catalan$数。具体可以参见$Catalan$数资料$.$

后缀表达式

后缀表达式是栈中一个很重要的分支,我们把它抓出来单独讲

表达式的概念

$  $众所周知,我们在平常的数学中试使用的使这样的式子:

  • $3*(2-1)$

$  $像这样,运算符号在运算数字中间的叫做中缀表达式$.$

根据中缀表达式的写法,又拓展出

  • $* 3 - 2 1 $

$  $像这样,运算符号在运算数字前面的叫做前缀表达式$.$

$  $同样可以拓展出

  • $ 2 1 - 3 *$

$  $像这样,运算符号在运算数字后面的叫做后缀表达式$.$

在这里,我们不再叙述各种表达式的算法,而是直接把目光放到后缀表达式在$c++$中的写法上来。


$  $我们首先考虑$dfs$的做法。在录入符号时,就返回运算的值。但这样程序复杂度高,且细节不容易处理,更别说时时间复杂度了,一般都是指数级别的。

$  $所以我们思考是否有更好的做法。记得$lxl$曾说数据结构可以让程序跑的更快,所以我们思考是否可以用数据结构解决这题。仔细研究后我们发现,后缀表达式始终满足符号最后录入这个条件,这也就意味着我们可以在线询问。于是,我们想到了一种使用栈的方法$.$

$  $我们设一个栈$P$,并遍历后缀表达式,如果是数字就入栈,如果不是就取出当前栈顶的两个元素进行运算。注意,这里要把结果入栈。当遍历完后,栈顶的元素就是答案。

下面是一些常见的问题:

$Q:$运算符号不用入栈么?
$A:$不需要。遇到运算符号计算就好,没必要入栈。

$Q:$为什么计算出来的结果要入栈?
$A:$因为后缀表达式是没有括号的,所以计算优先级一定,计算出来的结果下一步还要用。


栈的一些小技巧

手写栈嫌麻烦?使用$C++ STL stack!$

学习链接($link$)

$c++$的标准模板库$(STL)$中自带栈$(stack)$的函数。支持单调栈以及栈的所有基本操作(还包括您不知道的)!

栈为什么快?

  1. 栈是一种线性结构
  2. 栈的基本操作时间复杂度均为$O(1)$

$end.$

$By wwh$

$On 2019/8/17$

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!