关于阶乘的两个常见算法及一个相关面试题【zz】

sinxy 2008-05-20 04:24:44
阶乘的定义

阶乘是数学中的一个术语。对于一个非负整数n,n的阶乘指的是所有小于等于n的正整数的乘积,记为n!。例如,
[img=http://www.cnblogs.com/images/cnblogs_com/anderslly/WindowsLiveWriter/1ac39afd62b4_8F62/c018852104089c3b86ad5bba9e9223f9_2.png] [/img]
[img=http://www.cnblogs.com/images/cnblogs_com/anderslly/WindowsLiveWriter/1ac39afd62b4_8F62/4870ed458b29bbc1925d3466e81e4b0d_2.png] [/img]

符号n!是由Christian Kramp(1760 – 1826)于1808年引入的。

阶乘的严格定义为:
[img=http://www.cnblogs.com/images/cnblogs_com/anderslly/WindowsLiveWriter/1ac39afd62b4_8F62/f06eb9403ca1f410055f763de0b6bd9f_2.png] [/img]
并且0!=1,因为阶乘是针对所有的非负整数。
后者基于一个事实:0个数的乘积为1。这个是很有用的:
递归关系 [img=http://www.cnblogs.com/images/cnblogs_com/anderslly/WindowsLiveWriter/1ac39afd62b4_8F62/04f0de9cd29fc21e0bb3bf57a31a760b_2.png] [/img]适用于n = 0的情况;

这个定义使得组合学(combinatorics)中许多包含0的计算能够有效。

阶乘的概念相当简单、直接,但它的应用很广泛。在排列、组合、微积分(如泰勒级数)、概率论中都有它的身影。

但我这里最想说的是(与本文主题相关),在计算机科学的教学中,阶乘与斐波那契数列一道经常被选为递归算法的素材,因为阶乘满足下面的递归关系(如果n ≥ 1):
[img=http://www.cnblogs.com/images/cnblogs_com/anderslly/WindowsLiveWriter/1ac39afd62b4_8F62/3843d65ea89dde4c2c1c27f23d2b2fc7_2.png] [/img]
言归正传

下面来考虑如何在程序中计算阶乘。根据阶乘的定义和它满足的递归关系,我们很容易得到这样的算法:
public static long Calculate(int n)
{
if (n < 0) { throw new ArgumentOutOfRangeException("n必须为非负数。"); }
if (n == 0) { return 1; }
return n * Calculate(n - 1);
}


随着n的增大,n!会迅速增大,其速度可能会超出你的想象。如果n不大,这种算法还可以,但对long类型来说,很快就会溢出。对计算器来说,大多数可以计算到69!,因为70! > 10^100。

上面这种累积相乘的算法的主要问题在于普通类型所容纳的数值太小,即使是double类型,它的最大值不过是1.79769313486232e308,即一个309位的数字。

我们来考虑另外一种方案,将乘积的每一位数字都存放在数组中,这样的话一个长度为10000的数组可以存放任何一个10000位以内的数字。

假设数组为uint[] array = new uint[10000],因为1! = 1,所以首先置a[0] = 1,分别乘以2、3,得到3! = 6,此时仍只需要一个元素a[0];然后乘以4得到24,我们把个位数4放在a[0],十位数2放在a[1],这样存放结果就需要两个元素;乘以5的时候,我们可以这样进行:用5与各元素由低到高逐一相乘,先计算个位数(a[0])4 × 5,结果为20,这样将a[0]置为0,注意要将2进到十位数,然后计算原来的十位数(a[1])2 × 5,结果为10加上刚才进的2 为12,这样十位数是2,而1则进到百位,这样就得到5! = 120;以此类推……

下面给出上述方案的一个实现:
public static uint[] CalculateLargeNumber(int n)
{
if (n < 0) { throw new ArgumentOutOfRangeException("n必须为非负数。"); }

if (n == 0 || n == 1) { return new uint[] { 1 }; }
// 数组的最大长度
const int MaxLength = 100000;
uint[] array = new uint[MaxLength];
// 1! = 1
array[0] = 1;
int i = 0;
int j = 0;
// valid为当前阶乘值的位数(如5! = 120,此时valid = 3)
int valid = 1;

for (i = 2; i <= n; i++)
{
long carry = 0;
for (j = 0; j < valid; j++)
{
long multipleResult = array[j] * i + carry;
// 计算当前位的数值
array[j] = (uint)(multipleResult % 10);
// 计算进到高位的数值
carry = multipleResult / 10;
}
// 为更高位赋值
while (carry != 0)
{
array[valid++] = (uint)(carry % 10);
carry /= 10;
}
}
// 截取有效元素
uint[] result = new uint[valid];
Array.Copy(array, result, valid);
return result;
}


用这个方法可以得出70!是101位(1.1979 × 10^100),450!是1001位,而1000!有2568位。需要注意的是,结果数的最高位存放在数组的索引最大的元素中,所以打印结果时要按正确的顺序。

曾经的一道面试题
在某公司面试的时候曾经遇到这样的一个面试题:100!的后面会带多少个0?
这个问题该怎么分析呢?先找简单的情况来看,5! = 120,后面带着一个0,这个0是怎么产生的?1×2×3×4×5,应该是4×5产生的,而4 = 2×2,我们应该看到如果乘积的因子中包含2和5,就会产生在结尾的0。根据数论知识,我们知道任何大于1的整数都可以分解为若干个素数的乘积,那么如果我们把一个阶乘按此分解,其形式必然是2^a×5^b×p1^a1...pn^an,这样可以得到0的个数为Min(a, b)。这样我们就可以知道面试题的答案了。不过我们再深入看一下。

根据上面的分析,问题可以转化为阶乘分解后包含多少个2和5的因子。直觉告诉我,5的个数一定会少于2的个数,如果能证明这个,那么结论是:0的个数就是因子5的个数。

假设函数F2(n!)表示n!所包含的因子2的个数,可以证明F2((2n)!) = F2(n!) + n,比如当n = 2时,F2(2!) = 1,F2(4!) = 1 + 2 = 3。
令n = 2^t,可以得到F2(2^(t+1)!) = F2(2^t!) + 2^t,再根据数学归纳法,可以得到结论:F2(2^n!) = 2^n - 1。

类似地,假设函数F5(n!)表示n!所包含的因子5的个数,可以证明F5(5^n!) = (5^n - 1)/(5 - 1)。有了这两个结论,我们可以进一步确定:F5(n!) <= F2(n!)。(证明过程略,仍使用数学归纳法)

那么结论便是:0的个数就是因子5的个数。F5(5!) = 1,所以5!带1个0,即120;F5(10!) = 2,所以10!带2个0,即3,628,800。
...全文
3206 48 打赏 收藏 转发到动态 举报
写回复
用AI写文章
48 条回复
切换为时间正序
请发表友善的回复…
发表回复
Ricky-Lin 2010-07-24
  • 打赏
  • 举报
回复
好算法
__HelloWorld__ 2010-07-04
  • 打赏
  • 举报
回复
膜拜,up
yfh1970 2010-07-03
  • 打赏
  • 举报
回复
100×101×102×103×…×2009=21的n次方×m 问n最小是几(m、n均为自然数)
titth 2010-02-23
  • 打赏
  • 举报
回复
引用 10 楼 njwangchuan 的回复:
用唯一分解定理,可以很方便的计算,只要计算0-100中5出现的次数就可以了。

公式为:若P为素数,则n!中P的次数为:

[n/p]+[n/p^2]+...+[n/p^k],p^k <n,[]表示向下取整

100!中含5的个数为:20+4=24个,含2的个数为:50+25+12+6+3+1=97个


很受用
wl0005 2010-02-15
  • 打赏
  • 举报
回复
不错,分析很有道理,学些了!
weijianliang 2009-07-24
  • 打赏
  • 举报
回复
数学好的楼主,学习了。 
i_beat_you_001 2009-07-22
  • 打赏
  • 举报
回复
我也来凑个热闹,
1000! = 402387260077093773543702433923003985719374864210714632543799910429938512398629020592044208486969404800479988610197196058631666872994808558901323829669944590997424504087073759918823627727188732519779505950995276120874975462497043601418278094646496291056393887437886487337119181045825783647849977012476632889835955735432513185323958463075557409114262417474349347553428646576611667797396668820291207379143853719588249808126867838374559731746136085379534524221586593201928090878297308431392844403281231558611036976801357304216168747609675871348312025478589320767169132448426236131412508780208000261683151027341827977704784635868170164365024153691398281264810213092761244896359928705114964975419909342221566832572080821333186116811553615836546984046708975602900950537616475847728421889679646244945160765353408198901385442487984959953319101723355556602139450399736280750137837615307127761926849034352625200015888535147331611702103968175921510907788019393178114194545257223865541461062892187960223838971476088506276862967146674697562911234082439208160153780889893964518263243671616762179168909779911903754031274622289988005195444414282012187361745992642956581746628302955570299024324153181617210465832036786906117260158783520751516284225540265170483304226143974286933061690897968482590125458327168226458066526769958652682272807075781391858178889652208164348344825993266043367660176999612831860788386150279465955131156552036093988180612138558600301435694527224206344631797460594682573103790084024432438465657245014402821885252470935190620929023136493273497565513958720559654228749774011413346962715422845862377387538230483865688976461927383814900140767310446640259899490222221765904339901886018566526485061799702356193897017860040811889729918311021171229845901641921068884387121855646124960798722908519296819372388642614839657382291123125024186649353143970137428531926649875337218940694281434118520158014123344828015051399694290153483077644569099073152433278288269864602789864321139083506217095002597389863554277196742822248757586765752344220207573630569498825087968928162753848863396909959826280956121450994871701244516461260379029309120889086942028510640182154399457156805941872748998094254742173582401063677404595741785160829230135358081840096996372524230560855903700624271243416909004153690105933983835777939410970027753472000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

这个结果对不?
死牛胖子 2009-07-21
  • 打赏
  • 举报
回复
学习
Wilson咻咻咻 2009-07-21
  • 打赏
  • 举报
回复
thanks very much!
wdbjsh 2009-07-21
  • 打赏
  • 举报
回复
噢哦。。。。貌似还是考虑少了。。。

因为24*25的话也会出现多一个0的情况。同样75也会出现。原因是25是有两个5因子的。
所以结果应该是24个。。。。

如果是125的!的话125就得算3个5因子,那么就是125/5+125/25+125/125=25+5+1=31
denys 2009-07-21
  • 打赏
  • 举报
回复
不错,分析很有道理,学些了!
wdbjsh 2009-07-21
  • 打赏
  • 举报
回复
所有的乘法计算中,结果尾数为0的可能只有两种,
1、乘数或被乘数的尾数为0;
2、乘数或被乘数的尾数为5和某个偶数。

已知,n!,小于n的尾数为5的数字个数肯定小于偶数的数字个数,因此情况2,只需要知道尾数为5的数字个数,就能知道情况2产生的0的个数。

因为情况1的数字必然是5的偶数倍

因此纵横1、2情况,那么n!中0的个数,就是所有<=n的并且5的整数倍的数字的个数。所以100!的尾数0的个数应该是20
但是实际上为22,因为100对应的是两个0,而50对应的也是两个0,因为50*任意一个偶数可以多得到1个
Stilfler 2009-07-21
  • 打赏
  • 举报
回复
经典,学习了。
china_what 2009-07-21
  • 打赏
  • 举报
回复
顶下,
liujianwish 2009-07-21
  • 打赏
  • 举报
回复
mark
lchunl9707 2009-07-21
  • 打赏
  • 举报
回复
白吃一个
小人物2014 2009-07-21
  • 打赏
  • 举报
回复
最简单的阶层算法莫过这个了:杜思波 湖南 永州

        public Double Calculate(int number)
{
Double result=1;

for (int i = 1; i <= number; i++)
{
result = result * i;
}

return result;
}
论坛gao 2009-07-20
  • 打赏
  • 举报
回复
顶呱呱
zcw840421 2009-07-20
  • 打赏
  • 举报
回复
3k

liffe 2009-07-20
  • 打赏
  • 举报
回复
MARK
加载更多回复(28)

33,009

社区成员

发帖
与我相关
我的任务
社区描述
数据结构与算法相关内容讨论专区
社区管理员
  • 数据结构与算法社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧