所有可能的出栈序列问题及卡塔兰数的应用

今天在做数据结构课后练习题的时候,发现有一道题挺有意思的,问题是这样的:给定两个序列,给出算法用来判断第二个序列是否为以第一个序列为入栈顺序的出栈序列。比如给出了入栈的序列 EASY,那么给出一个算法判断ASYE是否为它的一个出栈序列。在还没有做出这个题目之前我想到了另外的一个问题:如果给出一个入栈的序列,那么怎么求出所有可能的出栈序列呢?那么我们先来解决第二个问题。假如你组合数学学的还行的话,这个时候你可能会想到在组合数学中的一个有名的数--卡塔兰数。(http://zh.wikipedia.org/wiki/卡塔兰数)

上面就是它的常见公式,很多问题都可以通过计算这个公式直接得到你想要的结果。比如给你了一个这样的题目:已知一个入栈序列为1,2,3,4,5,求所有可能的出栈序列总数有多少?简单的一算就知道了这个就相当于要计算C5 的值了,也许你已经知道答案了。那么我想问:你知道怎么去求出它的所有可能的序列而不是仅仅要出它的总数呢?此时,这个公式将显得无力。那么我们接下来分析一下该如何解决这个问题。当面对比较复杂抽象的问题的时候,我们总是可以通过列举简单的例子来发现解决问题的规律。为了简单起见,我们列举了1,2,3,4,5 这5个数作为入栈的序列,简单分析一下它的过程,也许就知道程序该如何写了,当数字1入栈的时候,此时的栈将面临两个选择,出栈?继续入栈。也就是说任一时刻,对都会面临两种选择,出栈还是入栈?这时,如果你对递归程序有一定的认识的话,也就你对程序的结构已经有思路了。(为了方便利用栈的数据结构采用了C++)

我写的程序大概是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#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. 模拟入栈和出栈的过程,查看是否一致。详细思路如下:

    举例:入栈序列为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中仍然有元素,则说明不是合法出栈序列;

  3. 这个比较简单,假如在入栈序列中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;
    }
    复制代码

    对于卡塔兰数的应用还有很多,发现它真的是一个很伟大的发现,有很多的问题都可以用它来解决,如果认真的研究它解决的所有这些问题,其实都和栈的序列都有一定的关系。




列车长的烦恼

Acceteped : 1822 Submit : 4148
Time Limit : 1000 MS Memory Limit : 65536 KB
 

Description

John是个小列车站的站长,每次列车在这里重新编组时他就很烦恼。因为站上只有一个人字形的编组轨道(如图),所有的列车车厢都是从人字轨的右边依次进去,从左边出来。但有一些编组顺序John总编不出来,John怀疑有些编组顺序是不可能完成的,可John又找不出那些是顺序是可以编组出,那些不可以。请你写一个程序帮助John辨别哪些编组可以完成,哪些不能完成。


输入:

第一行是一个整数K,表示有多少个测试用例,以后每行一个测试用例,每行为n+1个整数,第一个整数为n 表示有多少节车厢,后面n个整数表示需要编组成的顺序。比如说3节车厢,按照1,2,3依次入轨编组,可以在左边形成1 2 3,1 3 2,2 1 3,2 3 1,321。

 

 
输出:

每行输出一个测试用例的结果。如果可以编组输出Yes,否则输出No。

 

Sample Input

2 
3 3 1 2 
4 1 2 3 4 
 

Sample Output

No
Yes
 

Source

程序设计实践
#include<iostream>
#include<vector>
#include<stack>
using namespace std;
int k,n;
int main()
{
    int i,j;
    cin>>k;
    while(k--)
    {
        vector<int>a;
        vector<int>b;
        stack<int>c;
        int d;
        cin>>n;
        j=0;
        for(i=0;i<n;i++)
            a.push_back(i+1);
        for(i=0;i<n;i++)
           {
               cin>>d;
               b.push_back(d);
            }
        for(i=0;i<n;i++)
        {

            c.push(a[i]);
            if(c.top()!=b[j])continue;
            while(c.size()>0)
            {
                if(c.top()==b[j])
                {
                    j++;
                    c.pop();
                }
                else
                    break;
            }
        }
        if(c.size()!=0)cout<<"No"<<endl;
        else cout<<"Yes"<<endl;

    }
          return 0;
}




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值