《数学与泛型编程:高效编程的奥秘》一2.1 埃及乘法算法

2.1 埃及乘法算法

与所有的古文明一样,埃及的计数系统也没有按位置计数这一概念,而且无法表示0。于是,乘法计算起来就特别困难,只有少数受过训练的专家才会做。(你可以想象一下:如果自己只能像使用罗马数字那样来做运算,而且要计算的数字又很大,那么算起来是相当困难的。)
怎样来定义乘法呢?宽泛地说,我们可以认为乘法就是“把某物多次加到它自己上面”,如果说得严谨一些,那么可以分两种情况来定义:一种情况是乘以1,另一种情况是乘以一个大于1的数。
我们将乘以1的乘法运算,定义如下:
1a = a(2.1)
接下来需要定义另一种情况,也就是怎样根据某数的n倍来计算其n+1倍。有些读者或许已经发现这是一个归纳的过程,本书稍后会以更为正规的形式来使用归纳法。
(n + 1)a = na + a(2.2)
有一种办法可以计算n与a的乘积,那就是把n个a连加起来。然而这种办法对于较大的数字来说相当乏味,因为总共要计算n-1次加法才行。此算法可以用C++代码表示为:

上面这个函数中的两行代码分别与等式(2.1)及等式(2.2)相对应。和古埃及人计算乘法时的情况一样,a与n都必须是正数。
阿姆士所描述的乘法算法,古希腊人将其称为“埃及乘法”(Egyptian multiplication),而现在有很多人则把它叫做“俄罗斯农夫算法”(Russian Peasant Algorithm)。这种算法所依据的原理是:
4a =((a + a)+ a)+ a
=(a + a)+(a + a)
这个原理是根据加法结合律推算出来的:
a +(b + c)=(a + b)+ c
如果采用这个办法,那么只需把a + a的值计算一次就行了,这样可以降低加法运算的次数。
埃及乘法算法的思路是:反复地将n减半,并将a加倍,同时求出a的各种倍数,这些倍数与a的比值都是2的整数次幂。在那个时代,算法并不是用a或n这样的变量来描述的,而是以具体的数字举例,然后说:“同样的操作还可以运用在其他数字上面”。阿姆士自然也不例外,他以41×59(也就是说n = 41,a = 59)为例,演示了怎样通过下面这种表格来执行该算法:
1 √ 59
2 118
4 236
8 √ 472
16 944
32 √ 1888
表格左边那一列的数字都是2的整数次幂,而对于右边那一列来说,其中的每一个数字都是它上方那个数字的两倍(之所以要采用这种反复翻倍的办法来列表,是因为把同一个数字加到它自己上面计算起来要相对容易一些)。如果用二进制的形式来表示41这个数,那么值为1的那些二进制位就可以与表格中勾选的那些行分别对应起来。于是,这张表格实际上就相当于下面这个算式:
41×59 =(1×59)+(8×59)+(32×59)
该等式的右侧出现了几个59的倍数值,这些倍数值都可以通过对59进行适当的翻倍而计算出来。
由于该算法在将n减半的时候需要判断n是奇数还是偶数,因此尽管没有直接的证据,但我们依然能够推测出:古埃及人已经知道了奇数和偶数之间的区别。因为古希腊人宣称他们是从埃及人那里学到数学的,所以这一点是可以肯定的。如果把埃及人定义奇数和偶数的办法表示成现代的数学记号,那就是:

此外,我们还要依赖下面这个关系式:
odd(n) half(n)= half(n-1)
现在,就可以用C++代码把埃及乘法算法实现出来了:

odd(x)函数可以通过判断x的最低有效位来实现,而half(x)函数则可以通过对x右移来实现:

multiply1函数要执行多少次加法呢?每次调用该函数时,它都要执行a + a语句中的那个加法运算,而由于我们在对n减半的过程中会递归地调用该函数,因此总共要调用log n次。此外,有些时候还需要执行result + a语句中的那个加法运算,于是总的加法次数就是:

# +(n)= ?log n? +(v(n)-1)

其中的v(n)表示在n的二进制形式中有多少个值为1的二进制位,这个数量也称为种群计数(population count、pop count)。至此,我们已经把一个复杂度为O(n)的算法,优化成了复杂度为O(log n)的算法。
这个算法总是最优的吗?其实并不是这样。比方说,如果要计算某数与15的积,那么按照刚才的公式,总共需要执行的加法次数就是:

# +(15)= 3 + 4-1 = 6

然而实际上只需要像下面这样,执行5次加法就够了:

上面这种连加操作称为加法链(addition chain)。我们刚才找到了15这个数字的最优加法链(optimal addition chain)。尽管阿姆士所记录的算法在某些情况下并不是最优的,但它毕竟可以很好地应对许多数字。
习题2.1 对于每一个小于100的正整数n来说,寻找其最优加法链。
在阅读这部分内容的时候,你可能会发现,如果第一个数比第二个数要大,那么把两者交换之后再进行运算应该会更快一些(例如计算3×15,要比计算15×3更为容易)。确实是这样,而且古埃及人也知道这一点。但笔者此处并不打算把这个优化技巧运用到算法中,因为我们将要在第7章对算法进行泛化,使它不仅可以用整数当参数,而且还可以用整数之外的其他类型做参数,到了那个时候,参数之间的顺序就会变得很重要了。

时间: 2024-09-17 23:15:43

《数学与泛型编程:高效编程的奥秘》一2.1 埃及乘法算法的相关文章

《数学与泛型编程:高效编程的奥秘》一第2章 算法初谈

第2章 算 法 初 谈 摩西很快就学会了算术与几何. --这些知识是他从埃及人那里学来的, 埃及人最重视的研究科目是数学. --亚历山大的斐洛(Philo of Alexandria),<摩西生平>(Life of Moses) 算法(algorithm)是用来完成计算任务的一系列有限步骤.由于算法与计算机编程的关系特别密切,因此很多人或许认为,算法是一个来自计算机科学专业的概念.其实算法这个词已经有几千年历史了.数学中充满了各种各样的算法,有些算法我们天天在用,就连小学生计算加法时所用的竖式

《数学与泛型编程:高效编程的奥秘》一导读

前言 如果将计算机科学与数学分离,那么这两者的发展都会有很大困难.于是,我们就试图通过一些课程,把人类文明早期就有的数学活动与现代才有的计算机活动结合起来.本书正是基于这样一种课程而编写的.能够与友人Dan Rose合作,我深感荣幸.他的管理工作令我们团队能够把泛型编程的原则运用到搜索引擎的设计上来,而且他愿意把我原来那些相当分散的课程内容集结成现在这样一本连贯的书籍.Dan和我都希望读者能够喜欢我们这次合作的成果.--A.A.S. 大家将要阅读的这本书是根据"Algorithmic Journ

《数学与泛型编程:高效编程的奥秘》一2.2 改进该算法

2.2 改进该算法 从加法的执行次数上面来看,multiply1函数确实做得不错,但它毕竟执行了?log n?次递归调用.由于函数调用的开销很大,因此我们想把程序中的递归调用去掉,以避免此类开销. 在对函数进行转化时,我们所秉持的一项理念是:通用的工作实现起来经常比具体的工作还要简单(It is often easier to do more work rather than less.).就本例来说,我们要按照下面这种通用的方式进行计算: r + na 其中的r是一个会在运算过程中持续更新的值

《数学与泛型编程:高效编程的奥秘》一1.1 编程与数学

1.1 编程与数学 那么,这种泛型编程的想法是从哪里来的?我们又应该怎样来学习它呢?这种想法是从数学中衍生出来的,尤其与抽象代数(abstract algebra)这个数学分支有关.为了使大家能够理解这种编程方式,本书会对抽象代数做一些介绍,并着重讲解怎样从抽象的运算属性来认识对象.这个话题一般是数学专业的大学生才去研究的,然而笔者认为,它对于我们理解泛型编程会起到关键的作用.实际上,还有很多基本的编程概念也同样来自数学.对这些概念的产生及变化过程加以学习,能够促使我们更好地思考软件的设计问题.

《数学与泛型编程:高效编程的奥秘》一第1章 内容提要

第1章 内 容 提 要 不懂数学,就无法了解世界. --罗吉尔·培根(Roger Bacon),<大著作>(Opus Majus) 这是一本谈编程的书,但是它与大多数的编程书都不太一样,因为除了算法和代码之外,本书还会给出数学证明和一些讲述从古代到20世纪各种数学发现的历史材料. 另一个更为具体的特色在于:这是一本谈论泛型编程(generic programming)的书.泛型编程是出现于20世纪80年代的编程方法,在20世纪90年代随着C++标准模板库(Standard Template L

《数学与泛型编程:高效编程的奥秘》一1.3 阅读准备

1.3 阅读准备 由于书中的很多内容都和数学有关,因此你可能担心自己必须先具备丰富的数学知识,然后才能看懂这本书.其实你只要有逻辑思考能力就行(程序员应该很擅长逻辑思考),笔者并不会要求大家具备中学代数与中学几何之外的其他数学知识.某些章节可能会运用向量(vector)与矩阵(matrix)等线性代数(linear algebra)方面的概念,如果从前没有看过这方面的资料,那么把这些内容跳过去就可以了.若是对本书所用的记法不够熟悉,则请参考附录A. 数学中有一个很重要的部分就是对命题给出形式化证

《数学与泛型编程:高效编程的奥秘》一3.1 整数的几何属性

3.1 整数的几何属性 毕达哥拉斯(Pythagoras)这个名字,大多数人都是从同名的定理中听说的.这位古希腊的数学家和哲学家曾经提出一个理念:要想理解世界,就必须先懂得数学.他还发现了数字的很多奇妙性质,而且认为这些发现无论有没有实际的用途,其本身都具备巨大的价值.亚里士多德的学生亚里士多塞诺斯(Aristoxenus)曾经说道:"他极其重视算术研究工作,并把这些研究从实用的商业领域中划分出来,单独加以推进." 毕达哥拉斯(Pythagoras,约公元前570-公元前490) 毕达

《数学与泛型编程:高效编程的奥秘》一1.2 从历史的角度来讲解

1.2 从历史的角度来讲解 如果能把某个话题融入故事中,那么我们学起来就会更容易一些,而且也会觉得更为有趣.我们想要知道:在某个时间发生了什么事件?都有谁与该事件相关?这些人是怎样产生某种想法的?他们是要在别人的基础之上进行研究,还是要驳斥别人的结论?为此,本书在介绍数学概念时,还会讲一些与此有关的故事,并谈一下提出这些概念的人.笔者通常用一份简要的传记来描述身为故事主角的数学家,这些小传不会像百科全书里面写得那样详尽,它只是想令你对这位数学家的情况有所了解而已. 尽管我们会从历史的角度来谈论某

《数学与泛型编程:高效编程的奥秘》一3.7 本章要点

3.7 本章要点 古希腊人对数字的"形状"以及其他一些属性(例如是不是素数,是不是完美数等)很着迷,这就给数论这一数学领域打下了基础.他们所提出的某些算法(例如埃拉托斯特尼筛法)即便在今天来看,也是相当优雅的,只不过我们还可以通过某些现代的优化技术来继续提升其效率. * 读完本章之后,大家已经看到了两种能够证明是无理数的方法,一种是几何方法,另一种是代数方法.能够针对同一个数学现象提出两种完全不同的证明,这是相当好的结果,而且数学家实际上也必须像这样去寻找同一个数学现象的多种证法,以增