出栈序列研究
作者: 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个数依次入栈,出栈序列有几种?
递归的关键在于构造第推关系式,所以我们要定义一个描述结论的式子,在这里用表示n个数依次入栈时出栈序列的个数。构造第推式就悲剧了,当时我推出它是一个类似于Catalan第推式的式子,那个兴奋哟!但是现在怎么也想不起来了。哎,人生最痛苦的事莫过于此,如果哪位仁兄知道怎么推,一定要用email我啊。
没办法,我只能换一种方法了。用决策的思想,把解决问题的过程看成是决策的过程。假设编号为1,2,3,4,5,6,7的七个学生依次站成一列,对面是一 个栈。把自己想象成教官,你可以向学生施发两种号令:pop和push。当你喊push时对头的学生就走进栈里,如果对列中没人你就犯了一个错误,把它记 为。喊pop时栈顶的学生就出来,如果栈是空的,那么你也犯错了,把此错误记为。你现在要用口令(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指令数,否则就会犯错误,反应在序列中就是,对任意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的排列总数为 ,先把不满足条件的序列数求出来,然后再用 减掉就行了。对任意一个不满足条件的序列,我们找出第一个满足如下条件的m: 前m个数中0比1多1个。把这m个数反转,就变成了前m个数中0比1少一个,而整个序列中,0比1少2个。不满足的条件的序列和 由n-1个0、n+1个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
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, 0, sizeof(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 阅读(5769) 评论(6) 编辑 收藏 举报