Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1575199
  • 博文数量: 399
  • 博客积分: 8508
  • 博客等级: 中将
  • 技术积分: 5302
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-14 09:28
个人简介

能力强的人善于解决问题,有智慧的人善于绕过问题。 区别很微妙,小心谨慎做后者。

文章分类

全部博文(399)

文章存档

2018年(3)

2017年(1)

2016年(1)

2015年(69)

2013年(14)

2012年(17)

2011年(12)

2010年(189)

2009年(93)

分类: LINUX

2009-10-31 17:32:50

一楼:

*
* *
* * *
* * * *
* * * * *
       形如这样的直角三角形网格,从左上角开始,只能向右走和向下走,问总共有多少种走法?

       问题的由来:编号为 1 到 n 的 n 个元素,顺序的进入一个栈,则可能的出栈序列有多少种?

       对问题的转化与思考:n 个元素进栈和出栈,总共要经历 n 次进栈和 n 次出栈。这就相当于对这 2n 步操作进行排列。

       一个模型:一个 n*n 的正方形网格,从左上角顶点到右下角顶点,只能向右走和向下走。问共有多少种走法。如果将向右走对应上述问题的进栈,向下走对应上述问题的出栈,那么,可 以视此模型为对上述问题的具体描述。而解决此问题,只要在总共从左上角到右下角的2n步中,选定向右走的步数,即共有C(n 2n)中走法。

       但是存在一个问题,如果走法越过了对角线,那么对应到上述问题是出栈数比入栈数多,这是不符合实际的。

       对以上模型进行处理,对角线将以上正方形网格分成两部分,只留下包含对角线在内的下半部分,那么就不会出现越过对角线的问题。而这问题就是开始提出的问题。走法总数不等于C(n 2n)/2 ,因为C(n 2n)种走法中,有的越过了边界。

       问题等价于:n个1和n个0组成一2n位的2进制数,要求从左到右扫描,1的累计数不小于0的累计数,试求满足这条件的数有多少?
       解答: 设P2n为这样所得的数的个数。在2n位上填入n个1的方案数为 C(n 2n)不填1的其余n位自动填以数0。从C(n 2n)中减去不符合要求的方案数即为所求。不合要求的数指的是从左而右扫描,出现0的累计数超过1的累计数的数。不合要求的数的特征是从左而右扫描时,必 然在某一奇数2m+1位上首先出现m+1个0的累计数,和m个1的累计数。此后的2(n-m)-1位上有n-m个1,n-m-1个0。如若把后面这部分 2(n-m)-1位,0与1交换,使之成为n-m个0,n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一 个由n-1个0和n+1个1组成的一个排列。

       反过来,任何一个由n+1个0,n-1个1组成的2n位数,由于0的个数多2个,2n是偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在 后面的部分,令0和1互换,使之成为由n个0和n个1组成的2n位数。即n+1个0和n-1个1组成的2n位数,必对应于一个不合要求的数。

       用上述方法建立了由n+1个0和n-1个1组成的2n位数,与由n个0和n个1组成的2n位数中从左向右扫描出现0的累计数超过1的累计数的数一一对应。
         
        例如是由4个0和4个1组成的8位2进制数。但从左而右扫描在第5位(显示为红色)出现0的累计数3超过1的累计数2,它对应于由3个1,5个0组成的10100010。

反过来对应于 10100101因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应,故有P2n = C(n 2n)― C(n+1 2n)这个结果是一个“卡塔兰数”Catalan,在组合数学中有介绍,可以参阅有关资料。若有问题可以大家一起交流啊!还有种方法是用我提出的那个模型 的,但是那解法还没怎么弄明白,待我明白了到时候再和大家探讨!

二楼:

       楼主的解法很妙,我想是否可以用一种更合乎思维习惯的方式解决这个问题呢。
       可以把这个问题描述为一个二元组,(n, 0) 表示有n个元素等待进栈, 0 个元素已进栈, 这相当于问题最初的状况. 接着问题转化为(n-1,1). 可以这么说(n,0) = (n-1,1). 而对于(n-1,1)则相当于(n-1,0)+(n-2,2).
(n-1,0)表示栈中的一个元素出栈, (n-2, 2)表示又有一个元素入栈.
       把问题一般话,则(n,m)的排列问题可以转化为(n,m-1)+(n-1,m+1) 此时m>=1, 因为必须栈中有元素才可以出栈.当m=0则(n,0)的问题只能转化为(n-1,1).   当问题为(0, m)时得到递归边界,这个问题的解是只有一种排列.

我写了程序如下:

#include;

#define ELEMNUM 6;


int getPermuStack(int n, int m)
{
if(n == 0)//递归边界
return 1;
if(m == 0)//(n,0)问题的处理
return getPermuStack(n-1, 1);
return getPermuStack(n, m-1) + getPermuStack(n-1, m+1);
}


int main()
{
printf("The total count of stackout permutation is %d.", getPermuStack(6, 0));
return 0;
}

运行结果:The total count of stackout permutation is 132.和楼主得到的算式计算的结果相同.

三楼:

       这种方法是可行的,但在实际编程中最好不要用递归,这样如果递归次数一多就容易造成栈溢出。你可以试下用较大的参数来调用你的函数,会造成runtime error的。而且纯粹的递归会造成大量的重复计算:比如,你在计算getPermuStack(5, 5)的时候计算了getPermuStack(5, 4),然后在计算getPermuStack(5,3)的时候又计算了一遍。 当然可以通过动态规划的思想设置一个二维数组来记录计算结果,可是太消耗空间。所以说如果直观的方法就能很高效地解决问题的话,就不会有那么多人去从数学 上求解了。呵呵。

四楼:

       的确是这样的, 但递归的致命缺点,也是优点就是把复杂的计算留给机器, 递归往往能迅速简洁的思维求出问题的解, 也许得到的算法是低效的.所以递归应该是一种懒人算法. 而楼上说的"从数学上求解"应当是指从正向考虑问题的解, 递归是逆向求解. 对于楼主的算法是无可厚非的,也是相当巧妙的. 递归的方法也是数学方法的一种, 所以说如果用"数学方法"就能解决问题的话, 就不会有递归的求解了.
阅读(1249) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~