阶乘的定义
阶乘是数学中的一个术语。对于一个非负整数n,n的阶乘指的是所有小于等于n的正整数的乘积,记为n!。例如,
符号n!是由Christian Kramp(1760 – 1826)于1808年引入的。
阶乘的严格定义为:
并且0!=1,因为阶乘是针对所有的非负整数。
后者基于一个事实:0个数的乘积为1。这个是很有用的:
递归关系 适用于n = 0的情况;
这个定义使得组合学(combinatorics)中许多包含0的计算能够有效。
阶乘的概念相当简单、直接,但它的应用很广泛。在排列、组合、微积分(如泰勒级数)、概率论中都有它的身影。
但我这里最想说的是(与本文主题相关),在计算机科学的教学中,阶乘与斐波那契数列一道经常被选为递归算法的素材,因为阶乘满足下面的递归关系(如果n ≥ 1):
言归正传
下面来考虑如何在程序中计算阶乘。根据阶乘的定义和它满足的递归关系,我们很容易得到这样的算法:
C# code
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->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;以此类推……
下面给出上述方案的一个实现:
C# code
<!--
Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/
-->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。
原文链接 http://topic.csdn.net/u/20080520/16/c6968aa3-be5f-4592-85bc-b8739d698530.html
若原作者不同意,可立即删除
分享到:
相关推荐
C语言基础代码,在此程序里,利用简单的代码,达到输入任意一个数字,得到一列从1~输入数字的阶乘表,不支持负数。
在微机原理与接口技术的学习中,计算N的阶乘是一项常见的实践任务,它涉及到多个汇编语言编程和计算机系统底层操作的知识点。本实验旨在训练学生掌握子程序调用、递归算法、堆栈操作以及汇编指令的运用。 首先,...
在VB(Visual Basic)编程环境中,我们...同时,它也涉及到数学概念——阶乘和求和,这对于理解和应用计算机科学中的算法至关重要。通过实践这样的小程序,开发者可以提高逻辑思维能力,并加深对编程语言特性的理解。
本题目是关于计算阶乘之和的问题,主要涉及到高精度计算,包括高精度加法和乘法的实现。以下是对这些知识点的详细解释: 1. **高精度计算**:在计算机中,标准的数据类型如int、long等有固定的存储长度,无法直接...
高精度算法,几千以内的阶乘很快就可以算出而且不会溢出……我定义了一个25700位的存放结果的数组,可能要占用一点内存。^_^ winTC3运行。对做论文或毕业设计的朋友很有用哦~~~~
第 5 章累加与阶乘——循环结构 本章节主要介绍了循环结构的知识点,包括 for 循环、while 循环和 do-while 循环的语法形式和应用,复合赋值运算符的使用,以及递归调用方法的概念和应用。 循环结构 循环结构是...
### 计算机组成原理课程设计——微程序实现阶乘 #### 一、程序设计目的与基本原理 ##### 1. 程序设计目的 本课程设计的主要目的是让学生通过实践,更深入地理解计算机组成的原理及其应用。具体目标包括: - **...
大数运算——计算n的阶乘 (n≥20)。 【基本要求】 (1)数据的表示和存储: ①累积运算的中间结果和最终的计算结果的数据类型要求是整型——这是问题本身的要求。 ②试设计合适的存储结构,要求每个元素或结点...
### ARM汇编语言程序设计实例解析——阶乘操作 #### 概述 本文将深入探讨如何使用ARM汇编语言来实现阶乘运算,并且重点分析一个具体的实例:计算20!(即20的阶乘),并将计算结果的64位数值存储在寄存器[R9:R8]中...
可以算1000左右的大数阶乘,如果要算更大,改变数组的大小
一种常见方法是Karatsuba算法或Toom–Cook乘法,但这里我们将使用更直观的分治策略——“竖式乘法”。我们逐位地进行乘法操作,每次乘以一个较小的数,然后将结果累加到结果链表中。为了处理进位,我们需要保持两个...
然而,在实际应用中,我们经常需要计算大数字的阶乘,例如 TOJ 的 1016 题——“求 N!左边第二位的数字”。在这种情况下,使用 Stirling 逼近近似计算阶乘是一个非常实用的方法。 Stirling 公式的来源可以追溯到 ...
,这就是递归调用的精髓所在——每次调用都用不同的参数(N-1)替换N,直到达到基本情况,即N=1时,1! = 1。 在汇编语言中,设计递归子程序通常涉及以下几个关键步骤: 1. **过程定义和调用**:使用伪操作`PROC`和...
除了基本的乘法,`BigInteger`类还提供了`Pow`方法用于计算幂,这在实现阶乘的另一种方法——斯特林公式(Stirling's Formula)时可能会用到。斯特林公式可以近似地表示阶乘,从而避免直接的乘法运算,提高效率,但...
标题提到的“汇编大数阶乘”是指使用低级编程语言——汇编语言来实现计算大整数阶乘的功能,特别是针对3300这样的较大数值。这种算法设计的关键在于效率和精度,因为大数计算可能会涉及大量的加法、乘法和存储操作。...
总结,本例中的Java代码实现了计算五的阶乘的功能,展示了两种不同的计算方法——循环和递归。理解并掌握这些基础知识对于Java程序员来说非常重要,因为它们是编程思维的基础,并在实际项目中频繁出现。
java代码-解决求阶乘的问题java源代码 ——学习参考资料:仅用于个人学习使用
标题中的"SNTGT_number_Factorial_prime_"暗示我们要探讨的是关于数学中的一种特殊数字类型——阶乘素数。阶乘素数是指一个数n的阶乘(n!),即所有小于等于n且大于0的整数的乘积,恰好是一个素数。这个主题涉及到...
本篇文章将重点介绍MATLAB中的循环语句之一:`for`循环,并通过一个具体的示例——阶乘函数的实现来深入探讨其用法。 #### 一、MATLAB中的`for`循环语句 `for`循环是一种常用的迭代结构,它允许用户重复执行一段...
.NET框架为我们提供了处理大数(大于`int`或`long`能表示的数值)的类——`System.Numerics.BigInteger`。在这个问题中,我们要讨论如何使用.NET的`BigInteger`来实现大数阶乘的计算。`BigInteger`类在.NET ...