全是入门的一些东西.基本全是从别处抄的.
栈: 支持单端插入删除的线性容器. 也就是说,仅允许在其一端加入一个新元素或删除一个元素. 允许操作的一端也叫栈顶,不允许操作的一端也叫栈底.
数个箱子相叠就可以认为是一个栈,只能在最顶端加入一个新箱子或拿走一个箱子.
栈中的元素遵循后进先出(last in first out,LILO)的规律.即:更早出栈的元素,应为更早入栈者.
这是一个演示:
奇数行为栈中元素(右端可以进行插入删除),元素以逗号隔开, EMPTY表示栈为空
偶数行为进行的操作
EMPTY
插入10
10
插入20
10,20
插入51
10,20,51
插入10
10,20,51,10
删除一个元素
10,20,51
删除一个元素
10,20
插入30
10,20,30
删除一个元素
10,20
删除一个元素
10
删除一个元素
EMPTY
栈混洗问题
给出三个栈S1,S2,S3,一开始S1中含有1到n的n个数字且从栈顶到栈底数字依次为1,2,3,....n-1,n.
只有两种允许的操作:
A S1非空时从S1取出一个元素放入S2,
B S2非空时可以从S2取出一个元素放入S3.
最后S3中自底向上形成的序列称作一个栈混洗.
例如,如果S1中一开始有1,2,3,4四个元素,那么先进行4次A操作再进行4次B操作,将得到序列4,3,2,1.如果A操作和B操作交替进行,将得到序列1,2,3,4.
显然,栈混洗的结果并不唯一.
一个长为n的序列的栈混洗可以表示成n次A操作和n次B操作组成的一个操作序列. 而n次A操作和n次B操作组成的一个操作序列也可以表示唯一的一个栈混洗序列.
不同的操作序列必然得到不同的栈混洗,不同的栈混洗也必然对应不同的操作序列.
并不是所有含有n个A,n个B的序列都是合理的操作序列.例如BBBBAAAA,将导致我们尝试拿出空栈S2中的元素.
一个序列合理的条件是:对于任意m(1<=m<=2n),该序列的前m个操作中,B操作的数目不超过A操作的数目.只要满足这个条件就能保证任意时刻不会拿出空栈中的元素.
如何判断一个栈混洗序列是否是可能出现的栈混洗序列
例如,对于序列1,2,3,通过栈混洗可以得到[1,2,3],[3,2,1],[1,3,2],[2,1,3],[2,3,1],但是无法得到[3,1,2].如果使3最先出栈,那么就必须先令1,2入栈,从而2会在1之前出栈,只能得到[3,2,1]
任意给出一个n和一个排列,如何判断这个排列能否通过栈混洗得到?例如,n=5,序列为5,4,1,3,2,是否可能?
直接的思路是,直接根据我们要得到的序列,尝试进行A操作和B操作.例如,n=5时,要使序列的第一个元素为5,就必须一直进行A操作直到5出现在S2的栈顶.之后需要4,4恰好在S2的栈顶,于是弹出4.接下来需要的1不在栈顶,因此这个序列无法通过栈混洗得到.时间复杂度显然为O(n).其正确性也是显然的.
栈混洗与括号序列
首先只考虑由一种括号组成的括号序列.
()()()(),(()())(),((()))都是能够匹配的括号序列.)(,))(,())(都是不能够匹配的括号序列.
只要将A操作与左括号"("对应,B操作与右括号")"对应,栈混洗的合法操作序列就可以和能够匹配的括号序列一一对应.
例如,AABB对应(()),ABAB对应()()
按照我们之前的理解,如果保证序列中左右括号数目相同,那么我们只需要扫描一遍序列并维护一个计数器,初始为0,遇到左括号+1,遇到右括号-1,只要这个计数器的数值始终非负,就说明任意一个前缀中左括号的数目多于右括号的数目,等价于这个序列是能够匹配的括号序列.
另一种判断括号序列是否合法的方法:初始化一个空栈S,从左向右扫描序列,遇到一个左括号将其入栈,遇到一个右括号时判断栈中是否有一个左括号,如果有,那么这个左括号与当前遇到的右括号相匹配.如果栈为空,那么这个括号序列并不合法.通过这种方式,我们除了得知括号序列是否合法,还可以得知每个右括号具体是和哪个左括号匹配,还可以处理序列中出现了多种括号且只有对应种类的括号能匹配的情况.可以自行尝试,"维护一个计数器"的方式并不能方便地扩展到多种括号的情况.
拓展:允许循环移位的合法括号序列
允许将括号序列的最左侧元素拿到最右边,问能否通过若干次这样的操作使得括号序列合法.
首先这样的序列仍然需要满足左括号和右括号数目相同. 但是除此之外还需要满足什么条件呢?
令人惊讶的是,除此之外并不需要满足什么条件.只要左括号和右括号数目相同,就可以保证能够通过若干次循环移位使得括号序列合法.
给出一个构造方式:将左括号视为1,右括号视为-1,求取该序列的前缀和.如果前缀和中的最小值为0那么序列本身就是合法的.否则我们找到前缀和的最小值的出现位置(多个数值相同时取最左侧的),将这个位置之前的一段序列移位到右端,得到的即为合法的括号序列.构造方式的正确性不难证明.
括号序列计数
有多少个不同的含有n对括号的合法括号序列?这个问题等价于通过栈混洗可以得到多少个不同序列.也等价于,有多少个含有n个0,n个1的长为2n的字符串使得任意一个前缀中1的数目不少于0的数目.
假设答案为f[n].边界条件为f[0]=1,表示没有括号/没有元素时也算有一种方案(空串也是一种合法序列).
插播两条高中数学:
分类加法计数原理:做一件事有m类方法,每类方法分别有A1,A2...Am种做法,那么做这件事共有\(A1+A2+...+Am\)种方法
分步乘法计数原理:做一件事有m个步骤,每个步骤分别有A1,A2...Am种做法,那么做这件事共有\(A1*A2*...*Am\)种方法
好了,接下来我们来将所学的用于实践.
考虑最左侧的左括号匹配的右括号在什么位置.假设这个左括号和匹配的右括号之间有2i个括号(这个数目必须是偶数),这2i个括号排列成合法括号序列的方案数为f[i].匹配的右括号右边还有2n-2i-2个括号,将它们排列为合法括号序列的方案数为f[n-i-1],这两部分可以认为是两个步骤,是独立的,那么总的方案数是f[i]*f[n-i-1].
对于不同的i,我们可以认为是做一件事的不同种类的方法.
于是\(f[1]=f[0]*f[0],f[2]=f[1]*f[0]+f[0]*f[1],f[3]=f[0]*f[2]+f[1]*f[1]+f[2]*f[0]\),这是一个O(n^2)的计算方式.
这里我们得到的f[n]也叫Catalan数(卡特兰数),它还具有很多实际意义.
紫书330~331页给出了Catalan数的另一个实际意义:多边形三角剖分数目,并通过另一个O(n^2)的递推公式,推倒了O(n)的递推公式.
另一个常用的计算公式是f[n]=C(2n,n)/(n+1)=C(2n,n) - C(2n,n-1),C(2n,n)为组合数.