所有可能的出栈序列问题及卡塔兰数的应用
今天在做数据结构课后练习题的时候,发现有一道题挺有意思的,问题是这样的:给定两个序列,给出算法用来判断第二个序列是否为以第一个序列为入栈顺序的出栈序列。比如给出了入栈的序列 EASY,那么给出一个算法判断ASYE是否为它的一个出栈序列。在还没有做出这个题目之前我想到了另外的一个问题:如果给出一个入栈的序列,那么怎么求出所有可能的出栈序列呢?那么我们先来解决第二个问题。假如你组合数学学的还行的话,这个时候你可能会想到在组合数学中的一个有名的数--卡塔兰数。(http://zh.wikipedia.org/wiki/卡塔兰数)
上面就是它的常见公式,很多问题都可以通过计算这个公式直接得到你想要的结果。比如给你了一个这样的题目:已知一个入栈序列为1,2,3,4,5,求所有可能的出栈序列总数有多少?简单的一算就知道了这个就相当于要计算C5 的值了,也许你已经知道答案了。那么我想问:你知道怎么去求出它的所有可能的序列而不是仅仅要出它的总数呢?此时,这个公式将显得无力。那么我们接下来分析一下该如何解决这个问题。当面对比较复杂抽象的问题的时候,我们总是可以通过列举简单的例子来发现解决问题的规律。为了简单起见,我们列举了1,2,3,4,5 这5个数作为入栈的序列,简单分析一下它的过程,也许就知道程序该如何写了,当数字1入栈的时候,此时的栈将面临两个选择,出栈?继续入栈。也就是说任一时刻,对都会面临两种选择,出栈还是入栈?这时,如果你对递归程序有一定的认识的话,也就你对程序的结构已经有思路了。(为了方便利用栈的数据结构采用了C++)
我写的程序大概是这样子的:
#include <iostream> #include <stack> using namespace std; static int count; void outprint(stack<int> q){ while(q.size()!=0) { cout << q.top() << "-> "; q.pop(); } cout << endl; count++; return; } //q 存放入栈序列 //stk 用于模拟入栈过程 //output 用于存放可能的出栈序列 void allPopSeq(stack<int> q,stack<int> stk,stack<int> output){ if((q.size() == 0)&&(stk.size()==0)&&(output.size() == 5)) { outprint(output); return; } if(q.size()!=0){//入栈 int v = q.top(); stk.push(v); q.pop(); allPopSeq(q,stk,output); stk.pop(); q.push(v);//回溯恢复 } if(stk.size()!=0) //出栈 { int v = stk.top(); stk.pop(); output.push(v); allPopSeq(q,stk,output); output.pop(); stk.push(v);//回溯恢复 } return; } int main(int argc,char** argv){ int arr[5] = {1,2,3,4,5}; stack<int> stkValues; stack<int> stkOutput; stack<int> tmp; int i; for(i = 0;i!= 5;++i){ stkValues.push(arr[i]); } allPopSeq(stkValues,tmp,stkOutput); cout << count << endl; }
这样当我们再回头看一下最初的问题:判断一个序列是否为某一个序列的出栈序列,这个问题也许现在将看着很容易了。有几种思路可以用来解决这个问题:
-
先求出所有可能的出栈序列,然后再一一判断。(当然了,这种方法仅仅是为了提高自己的能力)
-
模拟入栈和出栈的过程,查看是否一致。详细思路如下:
举例:入栈序列为1,2,3,4,5 某一序列为 2,3,1,4,5 。首先选择1 入栈,然后查看序列2 是否相同,不同说明没有出栈,继续入栈2,继续查看 相同,说明2出栈,然后继续查看是否相同1和3不同,继续入栈3,查看和序列2中的头元素3一致,出栈,继续查看序列1中的1,和序列2中的1 一致,然后出栈。。。直到最终序列2为空;如果最后发现序列1为空的时候序列2中仍然有元素,则说明不是合法出栈序列;
-
这个比较简单,假如在入栈序列中p<q<r 且p,q,r也是入栈的顺序,那么在出栈序列中在r后面的比它小的元素按照降序排列。(这个可以在做题的时候快速判断)
下面是我的代码,采用的思路2
#include <iostream> #include <stack> using namespace std; int main(){ int arr[] = {1,2,3,4,5}; int arr2[] = {2,1,5,3,4}; stack<int> stk; int j = 0; for(int i = 0;i < 5;i++){ stk.push(arr[i]); if(stk.top()!=arr2[j])continue; while(stk.size()>0){ if(stk.top() == arr2[j]){ j++; stk.pop(); }else break; } } if(stk.size()!=0){ cout << "no" << endl; } else cout << " yes " << endl; return 0; }
对于卡塔兰数的应用还有很多,发现它真的是一个很伟大的发现,有很多的问题都可以用它来解决,如果认真的研究它解决的所有这些问题,其实都和栈的序列都有一定的关系。