阶乘的内涵

        阶乘不能不说是数学中最重要的概念之一,是指所有小于或等于该数的正整数连乘积,即1×2×3×…×(n-1) ×n。在诸多有趣的数字游戏中,阶乘问题无论怎么看起来都神神秘秘的,似乎总也琢磨不透。然而,只要是真命题,总有解决的办法,而且多数时候有许多优秀的解决办法。我们来看《编程之美》中提到的两个阶乘相关的问题。

        问题一:给定整数N,N!的末尾有多少个0呢?

        问题二:求N!的二进制表示中最低位1的位置。

        乍一看这两个问题,傻眼了,心里不禁犯了嘀咕:什么鬼问题!的确,我们脑海中已经有了原始的解决方案:用循环或递归求取N!,然后不就解决问题了吗?这确实是一个解决方法,而且确实可以得到最终答案。然而我们必须面对这样一个现实:当N值很大时,不要低估N!的值,溢出几乎是必然的结果。或许有人说程序实现中完全可以利用相关方法存储过大的结果,但即使如此,我们内心深处仍总在发问:有没有更简单的解决方法?

        回到问题一,经过仔细分析后得知,问题只需知道N!末尾有多少个0,求取N!的方法似乎走了弯路。在脑海中依靠简单的第六感,我们回想起这样一件事情:从小学奥赛开始,我们便被强制培训接受只有质数中只有2和5才可能产生一个0。这样的直觉有帮助吗?回想阶乘的定义,一切终于豁然开朗:只要对N!进行质因数分解,然后计算其中2和5共同出现的对数不就是问题一的答案吗?事实确实如此。

        将N!进行质因数分解得到N!=2x×3y×5z×…,问题一的答案便是2和5共同出现的对数,即min(x,y)。至此,问题一已经得到初步解决,然而要直接求取x和y以计算min(x,y)并不容易。一切似乎无从下手,绕了一个圈子难道又回到了起点吗?不要着急,在最困难的时候往往只要稍作坚持便会赢得胜利,这点无论在战场鏖战或者商场博弈中都是适用的。既然如此,我们从 的质因数分解结果从新入手。这时一个经验帮助了我们:2<5,因此能被2整除的整数出现频率比能被5整除的整数高。这点可以这样理解,较小的整数其公倍数出现周期较小,例如从1开始的整数中每隔一个便有一个2的倍数,而每隔5个才出现5的倍数。由此得知x>z,于是min(x,y)=z,即N!末尾0的数目即为N!的质因数分解结果中质因数5的指数。走到这里,问题一基本得到解决,剩下的便是利用程序实现了。如下C/C++代码计算N!=2x×3y×5z×…中5的指数z。

int Index5(int N)
{
	int ret = 0;
	for (int i=1; i<=N; i++)
	{
		int j = i;
		while (j % 5 == 0)
		{
			ret ++;
			j /= 5;
		}
	}

	return ret;
}
        上述代码中的循环使得程序有几分生硬感,追求美感的人并不十分满意。实际上,上述对代码中逐个遍历N!中区间[1,N]的方法并非唯一求解指数z的方法。可以利用这样的事实:指数z由5的倍数、25的倍数、125的倍数等之和得到,即对给定的整数N满足z=[N/5]+[N/52]+[N/53]+…,其中[N/f]表示N!中区间[1,N]所贡献的因子f的个数。因此,可对上述C/C++代码进行修改得到如下代码解决问题一。

int Index5(int N)
{
	int ret = 0;
	while (N)
	{
		ret += N / 5;
		N /= 5;
	}
		
	return ret;
}

        接下来分析问题二。似乎很久之间就遇到过类似的问题,记得我在小学参加奥赛时经常遇到诸如19991999的个位数字是多少的问题,那时候很害怕这样的题目,所以奥赛从来就没得过什么奖项,这一悲剧甚至延续到了其它各科的学科竞赛上。闲话撇开,我们来分析 的二进制表示中最低位1的位置。给个特例, N!=3!=6的二进制表示1010的最低位1在第二位。当然,先计算N!的值再进行判断是需要有着愚公移山的宝贵品质的,我们换个思路对问题先进性可能的转化。N!总为正数,正数在计算机中的反码等于本身。若N!其二进制末尾为0意味着什么?N!是偶数。那末尾是1呢?当然是奇数。此时问题二便豁然开朗了:当N!为奇数时,问题二的答案是第一位;当N!为偶数时,只要不断剔除末端的0直至其为奇数,则问题二的答案便是剔除末端0的次数。如何剔除末端的0呢?右移一位即可。右移一位意味着什么呢?没错,就是除以2。因此,剔除末端0的次数就是除以2的次数,换句话说,就是N!的因式分解中2的指数,当然这种因式分解要求其余因子的乘积都是奇数,而质因数分解满足这一分解,由此问题二本质上是求取N!质因数分解中2的指数。

        按照问题一的第二种解决方法,此时x=[N/2]+[N/2 2]+[N/2 3]+…,具体C/C++实现代码如下。

int lowestOne(int N)
{
	int Ret = 0;
	while(N)
	{
		N >>= 1;
		Ret += N;
	}
	return Ret;
}

        除去上述解法,《编程之美》中还提到了一种解决思路,即 含有质因数2的个数等于N减去N的二进制表示中1的数目,具体讨论参加《编程之美》相关内容。

        至此,关于阶乘的两个问题得到了解决。在分析过程中我们发现,阶乘的内涵不过就是对其各个乘积因子的分析,而阶乘的结果并不需要求解出来。我们再次看到了分析的力量,然而,并不是每个问题都可以分析解决,分析侧重理论上或思维上的讨论,而实际分析中经验往往是十分重要的。优秀的设计人员绝对不是单纯的理论高手,而是经过艰苦卓绝的实战历练积累了丰富作战经验的,正所谓将军应该是经过上甘岭战役的。因此,日后的研究过程中,应该全面锻炼自身能力,而不应厚此薄彼。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值