出栈序列与卡特兰数
出栈序列与卡特兰数
问题
有n
个元素组成的序列,如a, b, c, ......
,顺序入栈(入栈元素的顺序符合这个序列即可),入栈操作之间可以有出栈操作,也就是可以a
入栈、b
入栈、b
出栈……
有以下问题:
- 所有可能的出栈序列有多少种?
- 判断一个序列是否是可能的出栈序列。
- 对于有
n
个元素的元素序列S
,依次入栈,输出所有可能的出栈序列。
分析
假设元素序列为a, b, c, d, ..., z
。
n = 1
时,所有可能的出栈序列只有{ "a" }
,共1种;
n = 2
时,所有可能的出栈序列只有{ "ab", "ba" }
,共2种;
n = 3
时,所有可能的出栈序列只有{ "abc", "acb", "bca", "bac", "cba" }
,共5种;
由于栈的后进先出(LIFO)特性,所有可能的情况必然不是全排列的种数,因而我们从栈的特性开始分析。
毫无疑问,第一个入栈的元素必然为0
号元素。但是最后一个出栈的元素是不定的。
为什么呢?
对于k
号元素来说,如果它最后一个出栈,则1 ~ k-1
号元素肯定在它入栈之前就全部出栈,k+1 ~ n
号元素肯定在它出栈之前全部出栈,这对于\(\forall k=1,2,...,n\)成立。因此最后一个出栈的元素可以是任意一个。
需要注意的是,我们在出栈序列与入栈元素序列之间建立了一种对应关系:
graph LR subgraph 入栈元素序列 A["1 ~ k-1"] B["k"] C["k+1 ~ n"] end subgraph 出栈序列 D["1 ~ k-1"] E["n"] F["k ~ n-1"] end A --> D B --> E C --> F
注意:这种对应关系对于出栈序列的任意一个前缀子序列都成立。
由于上述这样一种递归关系,我们可以这样分析:
- \(n\) 个元素的元素序列,假设 \(f(n)\) 为所有可能的出栈序列种数。
- 序列的编号为:\(1\sim n\)。
- 设最后出栈的元素为元素序列的 \(k\) 号元素,\(k\) 取不同值,出栈序列的情况是相互独立的。
- 元素序列 \(1\sim k-1\) 与 \(k+1\sim n\) 号元素的入栈出栈序列是相互独立的。
- 则:\(f(n)=\sum_{k=1}^{n}f(k-1)f(n-k),\;f(0)=1\)
这就是卡特兰(Catalan)数。
它的递推式有: \[ f(n)=f(n-1)\cdot\frac{4n-2}{n+1}\;(n=1,2,...,n),\;f(0)=1 \]
\[ f(n)=\sum_{k=1}^{n}f(k-1)f(n-k)\;(n=1,2,...,n),\;f(0)=1 \]
它的解有: \[ h(n)=\frac{C_{2n}^n}{n+1}\;(n=0,1,2,...) \]
\[ h(n)=C_{2n}^n-C_{2n}^{n-1}\;(n=0,1,2,...) \]
递推式(1)即递推式的解摘自百度百科。
C语言代码
对于所有可能的出栈序列的种数问题已经解决了,那么如何判断一个序列是不是可能的出栈序列呢?
思路还是和我们的分析一致,就是检查每一个前缀子序列,看它是否满足如下两个条件:
- 在待判断序列中存在一个“位置”,使得在该位置之前的元素,它们的“标号”均小于待判断序列最后一个元素的“标号”;
- 在该位置之后的元素,它们的“标号”均大于等于待判断序列最后一个元素的“标号”;
1 | int Seq_Pop_Stack_N(char *S, char *T, int n){ |