1 / 28

高精度整数问题

算法与数据结构第二次作业解题报告. 高精度整数问题. 组合数和 Catalan 的精确计算. 福州大学 04 计算机 (3) 班 王建南 学号: 030402332. 问题描述. 编一个大整数模板类,用来做高精度整数(也就是任意位数的整数)的四则运算,包括 加法( addition ) 减法( subtraction ) 乘法( multiplication ) 除法( division ) 利用上面设计的大整数模板类精确地计算组合数和 Catalan 数。. 大整数的介绍.

amos-dunn
Download Presentation

高精度整数问题

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 算法与数据结构第二次作业解题报告 高精度整数问题 组合数和Catalan的精确计算 福州大学04计算机(3)班 王建南 学号:030402332

  2. 问题描述 • 编一个大整数模板类,用来做高精度整数(也就是任意位数的整数)的四则运算,包括 加法(addition) 减法(subtraction) 乘法(multiplication) 除法(division) • 利用上面设计的大整数模板类精确地计算组合数和Catalan数。

  3. 大整数的介绍 • 在某些情况下(如数据加密和科学计算等方面),我们要处理很大的整数,它无法在计算机硬件能直接表示的范围内进行处理。若用浮点数来表示它,则只能近似地表示它的大小,计算结果中的有效数字也受到限制(如C++中的Double类型有效位只有15位) 。若要精确地表示大整数并在计算结果中要求精确地得到所有位数上的数字,就必须用软件的方法来实现大整数的算术运算。

  4. 需要解决的问题 • 大整数的表示 在利于编程实现,同时便于提高运算效率的基础上选择数据结构。这个主要是考查数据结构。我们将会发现,在数据结构上的一个小改进对效率的提高有时会有很大帮助的。而数据结构在一定程度上是可以弥补算法的不足的。 • 大整数的运算 利用所选的数据结构,正确、高效地实现整数的四则运算。这主要考查算法。我们可以看到算法的选择对程序的效率是有绝对影响的。算法是决定程序效率的根本。

  5. 大整数的表示 • 由于大整数的位数太多,我们首先要做的是把它“拆”开来,放在若干个地方,然后建立不同存储地址之间的联系。很明显,线性数组(或者说是线性表)是首选。下面的存储方式很直观也是最容易想到的。 对于大整数1112223334445 我们可以用一维数组来表示: 数组下标: 存储数据:

  6. 大整数的表示 但我们可以发现这种表示方法的一个不足之处: 计算这个大整数加上9000000000000的结 果。如果用前面的方式存储,我们会发现进位没有 地方存放了。 数组下标: 大整数一: 大整数二: 结果: 1 为什么会出现这种情况?原因在于我们把大整数的 高位存放在数组的下标小的位置。而解决这个问题 的方法也很简单,就是把整数反过来存放。 进位没有存放的位置

  7. 大整数的表示 这个进位可以通过扩大数组的方法来存放 • 另一种表示方式 数组下标: 大整数一: 大整数二: 结果: 1 其实前一种表示方法还会有一个问题。就是高位的对 齐,这个我们可以通过减法观察到,这里就不说了。 基于以上原因,我们采用线性数组,同时把高位存放 在下标大的地方。虽然这样子存放我们看起来不那 么直观,但后面我们会看到这种存放方式的好处。

  8. 高精度加法运算 • 在确立了使用线性数组表示的大前提后,我们可以很容易地解决高精度加法。(这里为了简便,我就不用类实现了,同时假设那两个大整数都为正数。) 1.初始化数组,数组全部元素置为0 2.用字符串读入大整数,同时以反向存储的方式 存放在数组中 3.进位标志flag置为0。从数组低位开始进行加 法运算。这里只要注意flag的更新就行了。 4.反向输出结果

  9. 高精度加法运算 高精度加法举例 • 这里我们假设我们已读入两个大整数,并且也已经被分别反向存放在数组a和数组b中了。 • 加数a 8 7 4 2 5 9 7 800 • 加数b 4 8 3 1 4 8 5 1 9 0 • 结果c 2 6 8 3 9 7 3 00 1 • 进位e 1 1 0 0 0 1 1 1 1 0 • 上面的过程表示为 • 87952478+915841384=1003793862 减法亦可用相同的方法实现,只是现在标志换成借 位标记而已。 前面补0的原因是为了对齐

  10. 高精度加、减法小结及其改进 我们首先要明确的一点是,选择线性数组及反向存 储的方式并不是偶然的。我们可以列一个竖式,用 手工模拟854+87的加法就会理解为什么要这样子 处理了。 而我们同时会发现,一个数组元素只存储一个位的 方式有点浪费,虽然这很合乎我们的习惯。但可不 可以通过增加存储位数的方法来提高效率呢?毕竟 我们需要的这个一个位计算机只要4个二进制位就可 以表示了,而VC给我们分配的一个int却有32 位。

  11. 大整数的运算之加、减法的改进 我们来分析只存储一个位的大整数加法是怎样进行 的。 加数a 8 7 4 2 5 9 7 800 加数b 4 8 3 1 4 8 5 1 9 0 结果c 2 6 8 3 9 7 3 00 1 进位e 1 1 0 0 0 1 1 1 1 0 设i表示数组第i位数,则 c[i] = (a[i] + b[i] + e) % 10; e = (a[i] + b[i] + e) / 10;

  12. 大整数的运算之加、减法的改进 所以我们如果要进行多位存储的话,我们只要把上 面的计算式子改成 c[i] = (a[i] + b[i] + e) % M; e = (a[i] + b[i] + e) / M; 其中M为10的n次方,n为规定的位数。如,每个 数组元素都存储4个位,则M=10000。 这个改进并没有引起程序上大的变动,但它的对运 算次数的减少是很有用的。做每个元素存储n位的 加法,其加法的次数为只存储一位的1/n。这是因 为它增大了存储密度,所以运算速度也随之提升 了。

  13. 高精度加法程序 • flag = 0; • n = min(a的位数,b的位数); • M= 10000; • For (i=0; i<n || flag; i++) { c[i] = (a[i] + b[i] + flag) % M; flag = (a[i] + b[i] + flag) / M; }

  14. 习题 • 四川大学Online Judge 1001和1002 http://acm.scu.edu.cn/soj/problems.action • 第二届福州大学程序设计大赛Fibonacci数列http://acm.fzu.edu.cn/problem.php?pid=1060

  15. 高精度乘法 • 我们先来看一个小整数乘以大整数的乘法运算是如何进行的。 我们模拟一下587414251047*56的执行过程。 这里为了提高效率,56并没有必要被分成两个位去 算,而是利用多位存储的思想来运算。 大整数:7 4 0 1 5 2 4 1 4 7 8 5 乘数: 56 进位: 39 26 2 5 28 14 23 7 23 41 48 32 3 0 结果: 2 3 6 8 5 0 8 9 1 5 9 8 2 3 所以乘法的最后结果就是32895198058632 这里的关键就是进位不一定是一位的。其原理和加法多 位存储的运算是一样的。

  16. 高精度乘法 现在我们来看看大整数乘以大整数是怎样进行的。 还是先做手工模拟一下516541*4845412是怎么算 的。我们发现它的原型就是先进行小整数乘以大整 数,再把它们的和加起来的过程。其根本思想就 是: 设两个大整数分别为a,b(都已反向存储了)。 将b按10进制展开,b=b[0]+10*b[1]+100*b[2] +…+b[n]*10^n。其中b[i]为小于10的非负整 数。 则a*b=a*b[0]+a*b[1]*10+a*b[2]*100+…+a*b[n]*10^n。 而a*b[i]就是小整数乘以大整数。(后面乘10^k, 只要进行移位就可以实现了)

  17. 高精度乘法 高精度乘法算法流程如下: ①读入被乘数s1,乘数s2 ②把s1、s2分成n位一段,转成数值反向存在数组a,b中;记下b的长度m ③i=0; ④从b中取出第i位与a相乘,累加到另一数组c中;(注意:累加时错开的位数应是多少位) ⑤i++;检测i值:大于m则转⑥,否则转④ ⑥打印结果

  18. 高精度乘法程序 M = 10000; p = 大整数a的“位数”;//这里的位数是指多位存储时的位数 q =大整数b的“位数”; for (i=0; i<p; i++) { flag = 0; for (j=0; j<q || flag; j++) { c[i+j] += a[i]*b[j] + flag; flag = c[i+j] / M; c[i+j] %= M; } } 为什么结果存放在这一位?

  19. 高精度除法 联想我们是怎么进行除法运算的,就可以设计出一 个算法来实现它,具体就不再赘言了。算法流程: • 输入被除数a,除数b(判断除数是否为零,是则结束),转2; • j=length(a)-length(b),商Q为0,转3; • 截取a前length(b)位,赋给R,转4; • 从R中逐次减去b,直到减了K次后R小于b为止,转5; • 把K加到商数Q末尾,转6; • (--j>=0)?在R后面加上b[j],转4:转7; • 输出商数Q及余数R。

  20. 高精度除法 这里就不给出除法的程序了。 但在实现时需要注意 1.除法可以反向除也可以正向存储,只要做相应调整就可以 2.实现时需要定义一个函数来比较两个大整数的大小,同时要用到大整数减法 3.多位存储还是可以运用的。但细节上要小心

  21. 习题 • 四川大学Online Judge 1003、1004 • 北京大学Online Judge 1001 http://acm.pku.edu.cn/JudgeOnline/showproblem?problem_id=1001 • 求PI的精度值到10000位 • 求e的精度值到10000位

  22. 组合数和Catalan的精确计算 由于Catalan函数本身是一个组合数再除以一个相 对较小的整数,而除法在前面已经提过了。这里我 们就只看组合数的精确求值。 算法一: C(m,n)=C(m-1,n)+C(m-1,n-1) C(m,0)=1 利用上面的递归式,我们可以在O(n*大整数位数) 辅助空间的基础上设计出一个只用加法就能算组合 数的算法。但我们看出这在实际上是不实际的。当 m=10000,n=5000时,辅助空间就要达到 4*5000*3009Bytes约50几M的内存。实际 上,它在时间效率上也是很不理想的。

  23. 组合数和Catalan的精确计算 算法二: 利用C(m,n)=m!/(n!*(m-n)!) 对上式进行化简可变为 C(m,n)=m*(m-1)*…*(m-n+1)/n! 在实现上,我们如果先把m*(m-1)*…*(m-n+1) 算出来再去除以n!的话,时间和空间的消耗都是比 较大的。我当时的做法是

  24. 组合数和Catalan的精确计算 1.采用多位存储,数组每个元素存储4位 2.从m开始乘到(m-n+1)时,每乘一个数k就相就地除以一个数(m-k+1)。这样即可以保证结果还是整数的同时,减慢空间的消耗。 3.等乘数或除数够大时才进行相应的计算。如 算100*99*…*50/50!一开始时我们没必要马上 就把100乘到结果上去。而是等再乘上99后,再把 结果乘上9900。这样可以减少很多次计算。 最后我的程序计算 C(5000,2500)和C(10000,5000)/50001 差不多要1.7S才能出结果。对这个结果还是不大满 意。于是我又想了另一种算法。

  25. 组合数和Catalan的精确计算 算法三 分析一下算法二,程序运行时间主要花在高精度乘 法和高精度除法上。而乘法是必需的,效率也比除 法好很多,因此我们的着眼点就变成了,怎么去避 免除法或将除法转换成乘法或干脆就将除法去掉。 如果能将除法去掉当然是最好的了。但能不能做到 呢?答案是可以。 还是利用到算法二的那个化简后的式子。别看它挺 长的,但我们可以保证,它的计算结果一定是整 数。因为组合数本身就是一个整数,没有理由经过 化简后就变成带有小数的实数。

  26. 组合数和Catalan的精确计算 利用这点,联想到小学时经常做的分数化简, 只要先把分子进行分解,存放在一个数组 count中,count[i]表示分解后因子为i的个数。 再将分母也进行分解,但在分解的过程中,每次得 到一个因子k,我们只要count[k]--就可以了。 因为m的值不可能太大,而count最大只需o(m) 的辅助空间,所以在空间是可行的。 在时间效率上,因为避免了除法运算,所以效率上 肯定会有大的改进的。 利用和算法二一样的一些技巧后,我的程序计算 C(5000,2500)和C(10000,5000)/50001 只要0.7S就可以得到结果了。

  27. 总结 其实算法三还是可以再做改进的,如可以预先存 储一个素数表,便于分解因数。同时因为分解因式 后,我们要计算的其实就是ai^pi其中ai为素数, pi为正整数。这个可以利用二分的方法进行改进的 ,只要利用我们前面写的模板类。。。。 我们看到这题不论在数据结构和算法改进上都有一 些小技巧。选择的数据结构要便于编程实现并要能 够有效地解决问题,但一般在数据结构方面能做的 改进不是很多。而算法就不一样了,它是整个程序 的灵魂,在算法上改进的余地一般也是比较大的。 我们只有在掌握基本数据结构的基础上,不断地学 习、改进算法才能有效地解决这类题目。

  28. Thanks For Attention

More Related