出栈序列研究

作者:  John Waken
邮箱:  JohnWaken@163.com
转载请著明: http://www.cnblogs.com/john-d/archive/2009/12/29/1635161.html


在学习数据结构的时候,老师出了这么个题目:1,2,3,4,5,6,7这七个数按顺序入栈,出栈序列有几种?当时我是先画了几个,比如1,2,3,4,5,6,7肯定可以,还有7,6,5,4,3,2,1也行,但是3,1,2,4,5,6,7就不合法。先考虑简 单的特殊情况是一种很好的思考方法,《concrete mathematics》就说过" smart mathematicians are not ashamed to think small ”,当然啦,我只是一个little programmer,不过每个人都应该提醒自己 think small。

还有另一个非常重要的思考方法——递归,特别是在计算机算法领域,我认为它是最重要的。递归首先要求把问题一般化,此题应该泛化为这样:1,2,3,... ,n这n个数依次入栈,出栈序列有几种?

递归的关键在于构造第推关系式,所以我们要定义一个描述结论的式子,在这里用C_{n} 表示n个数依次入栈时出栈序列的个数。构造第推式就悲剧了,当时我推出它是一个类似于Catalan第推式的式子,那个兴奋哟!但是现在怎么也想不起来了。哎,人生最痛苦的事莫过于此,如果哪位仁兄知道怎么推,一定要用email我啊。


没办法,我只能换一种方法了。用决策的思想,把解决问题的过程看成是决策的过程。假设编号为1,2,3,4,5,6,7的七个学生依次站成一列,对面是一 个栈。把自己想象成教官,你可以向学生施发两种号令:pop和push。当你喊push时对头的学生就走进栈里,如果对列中没人你就犯了一个错误,把它记 为\alpha 。喊pop时栈顶的学生就出来,如果栈是空的,那么你也犯错了,把此错误记为\beta 。你现在要用口令(push还是pop来指导学生的动作(入栈还是出栈),每个学生都入栈一次且出栈一次,所以总共要喊14次。
 
这样问题就转化为一个决策模型了,教官连续喊14次口令,每次都要决定是喊push还是pop,然后学生会根据指示进行动作。如果教官一次错误都没犯,出 栈的学生序列就是一个解,一个正确的决策序列对应了一个解。还是看一些例子吧(用1表示push指令,0表示pop指令):

     指令序号     1   2   3   4   5   6   7   8   9   10   11   12   13   14    
     指令内容     1   0   1   0   1   0   1   0   1   0    1    0    1    0
     出栈序列         1       2       3       4       5         6         7
     说明  1,2,3,4,5,6,7是一个解

     指令序号     1   2   3   4   5   6   7   8   9   10   11   12   13   14    
     指令内容     1   1   1   0   0   0   1   1   1   0    0    0    1    0
     出栈序列                 3   2   1               6    5    4         7        
     说明  3,2,1,6,5,4,7是一个解

     指令序号     1   2   3   4   5   6   7   8   9   10   11   12   13   14    
     指令内容     0   0   1   0   1   0   1   0   1   0    1    0    1    0
     出栈序列     错
     你一上来就发个pop指令肯定出错啊,因为这时候栈里一个学生都没有

     指令序号     1   2   3   4   5   6   7   8   9   10   11   12   13   14    
     指令内容     1   1   1   1   1   1   1   1   1   0    1    0    1    0
     出栈序列                                         错            
     老哥,前七次你已经把学生都叫到栈里去了,队伍里已经没人了!

现在来找找决策序列应该满足什么条件。首先每个学生都进栈出栈各1次,所以push指令和pop指令应该各占一半。如果有n个学生,序列中就有n个1和n个0。还有,到任意时刻为止你发送的push指令的个数应该不少于pop指令数,否则就会犯错误\beta ,反应在序列中就是,对任意k来说,序列前k个数所含1的个数一定不能少于0。哈哈,这样就得到了此题的数学本质:
     7个0,7个1组成一个序列(下标从1开始),要求对任意k来说,前k个数中,1的个数不能少于0。问这样的序列总共有多少个?
再进一步一般化为:
     n个0,n个1组成一个序列(下标从1开始),要求对任意k来说,前k个数中,1的个数不能少于0。问这样的序列总共有多少个?

我的能力就到此为止了,这个数学问题我解决不了,Knuth的《TAOCP》卷一给出了解答,利用了反向思维和一一对应的思想。
n个0和n个1的排列总数为  C_{2n}^{n} ,先把不满足条件的序列数求出来,然后再用  C_{2n}^{n} 减掉就行了。对任意一个不满足条件的序列,我们找出第一个满足如下条件的m: 前m个数中0比1多1个。把这m个数反转,就变成了前m个数中0比1少一个,而整个序列中,0比1少2个。不满足的条件的序列和 由n-1个0、n+1个1组成的序列一一对应,总数为C_{2n}^{n-1}

所以结果是C_{2n}^{n} -\quad C_{2n}^{n-1} \quad\quad= \quad\quad \frac{C_{2n}^{n}}{n+1} , 果然是一个catalan数。

终于结束了,n个数依次进栈时,出栈序列是n的catalan数。这个结论没什么,重要的是思考过程,特别是怎样把问题转化为一个数学问题。上面那个数学题的证明我描述的很不严谨,多数都是想当然,如果真要写证明的话会很复杂,有兴趣可以发email给我。由包建强兄提醒,我利用这个过程(注意是思考过程,从结论中你得不到任何东西)写个算法吧。

题目描述:

整数1到n按顺序入栈,请判断某个出栈序列是否可能存在。

Input:

第1行是序列的元素个数n(0<n<1000)

第2行是出栈的顺序a1,a2,...,an (ai互不相同且1<=ai<=n)

Outpu:

若出栈序列能存在就输出yes,否则输出no

 

poppush.c
 1 #include <stdio.h>
 2 
 3  #define MAX_SIZE 1000
 4 
 5 int pop[MAX_SIZE+1];
 6 int flag[MAX_SIZE+1];
 7 
 8 /* 
 9  * match程序是基于以下结论.
10  * 一个合法的pop序列满足这样的关系:
11  * 对任何两个相邻的数(ai, ai+1),
12  * 若ai+1 > ai,则ai+1 大于前面所有的数
13  * 若ai+1 < ai,则比ai+1小1的数肯定在前面已经出现
14  *
15  * 这个结论我已经严格证明了,你知道我当时有多兴奋吗!
16  */
17 int match(int pop, int size)
18 {
19     int i;
20     int max = 0;
21 
22     memset(flag, 0sizeof(flag)) /* 所有标志都清0 */
23 
24     for (i=1; i<=size; i++) {
25         if (pop[i]>pop[i-1&& pop[i]>max) {
26             max = pop[i];
27         } else if (pop[i]<pop[i-1&& flag[pop[i-1]]) {
28             flag[i] = 1;
29         } else {
30             return 0;
31         }
32     }
33     return 1;
34 }
35 
36 int main(void)
37 {
38     int n;
    
int i;
39     
40     /* 读入pop序列,下标从1开始 */
41     scanf("%d"&n);
42     for (i=1; i<=n; i++) scanf("%d"&pop[i]);
43 
44     /*
45      * 我很喜欢用数组第0项作哨兵,
46      * 它让程序更一致,
47      * 简单性和一致性应该是所有软件追求的目标。
48      */
49     pop[0= 0;
50 
51     if (match(pop, n)) printf("yes");
52     else printf("no");
53 
54     return 0;
55 }

posted on 2009-12-29 16:32  John Waken  阅读(5764)  评论(6编辑  收藏  举报

导航