以前一直以为1,2,3,4依次入栈可能出栈的顺序只有一种,那就是4321,因为一直认为栈是先进后出的,所以…..,最后终于知道事实上在后面的元素入栈之前前面的元素可能会出栈,比如在4入栈之前321就已经依次出栈了,那出栈序列则为3214;那么当每一个元素都满足刚入栈就立即出栈时则出栈序列为1234,这个序列是我当时最想不通的了,因为一直信奉“先进后出”,呵呵。以前对这部分也是很迷糊的,前段时间在编写二叉树的非递归的先序遍历算法时,终于意识到出栈次序的重要性,所以后来好好研究,终于解决了这个问题的一大半。
先说下解决这个问题的主要方法:分冶法+递归。
现考虑a1,a2,a3,…,an共n个元素依次入栈(a1,a2,a3,…,an这n个元素不一定是有序的,或者说这n个元素可以是任意顺序,但是应该必须保证每个元素是唯一的):
设用S(i,n)表示“a1,a2,a3,…,ai,…,an这n个元素中第i个元素ai最先出栈时所有的排列数”,那么当ai出栈后,紧接着ai出栈后的出栈元素必然是{ a(i-1), a(i+1), a(i+2), … ,a( i + n-i ) }这(n-i+1)个元素中的某一个元素,现在先假设i>1以使a(i-1)有意义——这是因为这n个元素是依次入栈的,所以当ai最先出栈时,必然ai前面的任何一个元素(指的是先于ai入栈的元素)已经入栈并且未曾出栈(因为ai最先出栈),那么紧接着ai出栈的必然是ai后面的元素中的一个(因为后面的元素后入栈,所以可以是任意的元素紧接着ai出栈)或者a(i-1),因为对于前(i-1)个已经入栈但却还未出栈的元素中,必须等到a(i-1)出栈后,a(i-2),a(i-3),…,a1这些元素才有机会出栈,说得土的一点就是,a(i-1)堵住了前面的(i-2)个元素!
1、对于a(i-1)紧接着ai出栈时:a(i-1)是{ a1,a2,a3, …, a(i-1) , a(i+1), a(i+2) , … , an }共(n-1)个元素第i-1个元素,所以这种情况下所有的出栈的排列数为S( i-1, n-1 )
2、对于a(i+1)紧接着ai出栈时:a(i+1)是{ a1,a2,a3, …, a(i-1) , a(i+1), a(i+2) , … , an }共(n-1)个元素第i个元素(注意哦),所以这种情况下所有的出栈的排列数为S( i, n-1 )
3、对于a(i+2)紧接着ai出栈时:a(i+2)是{ a1,a2,a3, …, a(i-1) , a(i+1), a(i+2) , … , an }共(n-1)个元素第(i+1)个元素(注意哦),所以这种情况下所有的出栈的排列数为S( i+1, n-1 )
.
.
.
(n-i+1)、对于a( i + n-i )[即an]紧接着ai出栈时:an是{ a1,a2,a3, …, a(i-1) , a(i+1), a(i+2) , … , an }共(n-1)个元素第(n-1)个元素(注意哦),所以这种情况下所有的出栈的排列数为S( n-1, n-1 )
那么根据加法原理,得到:
S(i,n) = S( i-1, n-1 ) + S( i, n-1 ) + S( i+1, n-1 ) + S( i+2, n-1 ) + … + S( i+ n-1-i , n-1 )
这里共有(n-i+1)个数相加
显然当i=1时,前面不可能有元素会紧接着a1出栈(说过有a0这个元素吗?),所以为了i=1时满足上式,规定而且事实上也应该是S( 0, n ) = 0 ,那么当i=n时,显然S( n, n ) = 1 ,因为第n个元素(最后入栈的元素)最先出栈时其他元素都是被前面一个元素堵住了,所以只有一种排列。
对于上面的公式:当取i的值为(i+1),则有
S(i+1, n) = S( i, n-1 ) + S( i+1, n-1 ) + S( i+2, n-1 ) + … + S( i+ n-1-i , n-1 )
这里共有( n -(i+1) +1) 个数相加
二式相减得到:
S( i,n ) – S( i+1, n ) = S( i-1, n-1 ) 即 S(i,n) = S(i-1, n-1) + S(i+1, n )
另外S( 0, n ) = 0 且 S( n, n ) = 1
所以这样就能算出S( i,n )
然后是a1,a2,a3, …, an共n个元素依次入栈后所有的可能出栈的排列数为T(n) , 则
T(n) = S( 1, n ) + S( 2, n ) + S( 3, n ) + … + S( n-1, n ) + S( n, n )
最终应该求出T(n) , 很遗憾的是在高中时我没能好好学习排列组合知识,所以目前无法用现有的数学运算符来表达出T(n)或S(i,n),或者说我求不出来T(n)或S(i,n),如果有人能够算出来,请通知我一下!
所以我只得编写程序求解这个问题了,因为计算机的计算能力很强!
#include <iostream> using namespace std; // S( i, n ) = S( i-1, n-1 ) + S( i, n-1 ) + S( i+1, n-1 ) + … + S( i+n-1-i, n-1 ) // S( i, n ) – S( i+1, n ) = S( i-1, n-1 ) // S( i,n ) = S( i-1, n-1 ) + S( i+1, n ) int S( int i, int n ) // a1,a2,a3,…,ai,…,an这n个元素中第i个元素ai最先出栈时所有的排列数 { if( i == 0 ) // S( 0, n ) == 0 return 0 ; else if( i == n ) // S( n, n ) == 1 return 1 ; else return S( i-1, n-1 ) + S( i+1, n ) ; // S( i,n ) = S( i-1, n-1 ) + S( i+1, n ) } int T( int n ) // a1,a2,a3, …, an共n个元素依次入栈后所有的可能出栈的排列数 { int sum = 0 ; for( int i=1 ; i <= n ; i++ ) sum += S( i, n ) ; return sum ; } int factorial( int n ) // 计算全排列 { if( n == 1 || n == 0 ) return 1 ; return n*factorial( n-1 ) ; } int main() { int i, j, f, t ; for( i=1 ; i<=13 ; i++ ) { for( j=1 ; j<=i ; j++ ) cout << S( j, i ) << ‘ ‘ ; // 先输出各项S(i,n)的值,当然S(0,n)就不必了输出了 f = factorial( i ) ; // 计算全排列以便对比 t = T( i ) ; cout << “/nT(” << i << “)=” << t << ‘/t’ << i << “!=” << f << ‘/t’ // 输出结果 << “T(” << i << “)/” << i << “!=” << (float)t/f <<“/n/n” ; // 将T(n)与n!对比 } return 0 ; }
运行结果如下:
1
T(1)=1 1!=1 T(1)/1!=1
1 1
T(2)=2 2!=2 T(2)/2!=1
2 2 1
T(3)=5 3!=6 T(3)/3!=0.833333
5 5 3 1
T(4)=14 4!=24 T(4)/4!=0.583333
14 14 9 4 1
T(5)=42 5!=120 T(5)/5!=0.35
42 42 28 14 5 1
T(6)=132 6!=720 T(6)/6!=0.183333
132 132 90 48 20 6 1
T(7)=429 7!=5040 T(7)/7!=0.085119
429 429 297 165 75 27 7 1
T(8)=1430 8!=40320 T(8)/8!=0.0354663
1430 1430 1001 572 275 110 35 8 1
T(9)=4862 9!=362880 T(9)/9!=0.0133984
4862 4862 3432 2002 1001 429 154 44 9 1
T(10)=16796 10!=3628800 T(10)/10!=0.00462853
16796 16796 11934 7072 3640 1638 637 208 54 10 1
T(11)=58786 11!=39916800 T(11)/11!=0.00147271
58786 58786 41990 25194 13260 6188 2548 910 273 65 11 1
T(12)=208012 12!=479001600 T(12)/12!=0.000434262
208012 208012 149226 90440 48450 23256 9996 3808 1260 350 77 12 1
T(13)=742900 13!=1932053504 T(13)/13!=0.000384513
本来用截图做结果,但是可惜不能上传图片,这个程序在gcc和VS2008里面测试通过,关于递归的能否结束,其实只要你注意一下每次递归调用后i的变化就能知道递归是否会最终结束,因为0<= i <=n,注意到这一点就可以很容易了解递归最终会结束,但是我还是不期望以不合法的数据调用函数,在调用函数时请确保0<= i <=n ,由运行结果可以看出T(n)的变化远远没有n!的变化快,因为T(n)只不过是累加的过程,所以尽管S(i,n)被调用多次,但由于每次计算都是加,所以计算还是很快的(0.133s)。注意理解S(i,n)表示“a1,a2,a3,…,ai,…,an这n个元素中第i个元素ai最先出栈时所有的排列数”,这是求解问题和运用递归的关键!现在解决这个问题算是有头绪了,准备再写程序输出所有的可能的出栈序列。