趣味数学和Python编程
上QQ阅读APP看书,第一时间看更新

6.2 辗转相除法

辗转相除法也叫欧几里得算法,是一种非常古老的求解两个数的最大公约数的算法。其基本的原理:两个正整数ab(设a>b),它们的最大公约数(英文简写为gcd)等于a除以b的余数rb之间的最大公约数。我们举个具体的例子来看一下。

例子:求(319,377),注意下面算式中的“÷”号为整除的意思。

因为319÷377=0(余319)

所以(319,377)=(377,319);

因为377÷319=1(余58)

所以(377,319)=(319,58);

因为319÷58=5(余29)

所以(319,58)=(58,29);

因为58÷29=2(余0)

所以(58,29)=29;

所以(319,377)=29。

根据上面的原理,辗转相除法的算法流程可以如下:

步骤1:计算ab的余数r

步骤2:如果r为0,则返回gcd(ab)=b,否则,转到步骤3。

步骤3:使用b的值更新a的值,使用余数r更新b的值,转到步骤1。

代码如下:

等等,程序中为什么不对ab的大小进行判断呢?上面的算法原理中不是要求a大于b吗?如果调用时a值大于b值,比如a为51,b为21,那么情况跟上述算法原理是相符的。如果调用时a值小于b值,比如a为21,b为51,那么,21除以51的余数r为21,不为0,于是接着调用gcd(51,21),发现了吗?这就和a>b的情况一样了。也就是说我们根本无须判断ab的大小,当a值小于b值时,算法的下一次调用就能够将ab的值交换过来。

“a%b”是什么运算?a%b就是a整除b以后的余数,比如5%3为2,3%5为3,8%4为0,4%8为4。

以上两种算法都可以计算任意两个数的最大公约数。在什么条件下,哪一种算法的速度更快,大家可以用如下的代码来验证一下,然后自己分析结果。

注意:这个代码中用到了time.perf_counter()函数,这个函数的功能就相当于高精度计时器的启/停开关,并把时间读数返回,显然我们需要在代码的最初import time。

学习了求最大公约数的方法以后,我们发现,欧几里得游戏的过程恰好是用辗转相减法求最大公约数的过程。回到前面的故事,科尔研究出的这个游戏的必杀技是怎样的呢?

科尔研究的结论有以下3点:

(1)这个游戏不是碰运气,胜负中含有数学规律。

(2)两个棋子的个数确定以后,必须仔细分析最后决定胜负的那一次选择会在什么条件下出现,一定要想法把最后控制胜负的那一次选择权掌握在自己手中,否则必输。

(3)推演出决定胜负的那一个局面之后的两条路线,选择必胜的那条路线。

我们以科尔输的例子(33,7)来说明,因为33=4×7+5,所以无论怎么取子,最后一定会出现(5,7)这个局面,用前面学过的最大公约数的符号来描述就是:(33,7)=(5,7),出现(5,7)这个局面时,接下去要取子的这个人没有选择,他只能从7中取5,那么就会出现(5,2)这个局面,用数学符号描述就是:(33,7)=(5,7)=(5,2),出现(5,2)这个局面以后,本轮掌握选择权的人,就有两种选择:

(1)取2个子,剩下(3,2),接下去的局面就是对手只能取2个子,剩下(1,2),自己最终获胜。

(2)取4个子,接下去的局面就是(1,2),则对手胜。

很显然,谁掌握最后的(5,2)这个局面,谁就具有选择权。因此再往前推演,就是如何取子可以迫使对手把(5,2)这个局面剩下来留给自己。

掌握了这个诀窍以后,我们来尝试一下,(64,5)如何取子可以导向胜利呢?

容易知道(64,5)=(4,5)=(4,1),谁被迫选择(4,5)谁就输,那么取子时,就要想办法迫使对手选择(4,5)。怎么取子你会了吗?

作业

1.用程序实现从键盘接收用户输入的一个数字n,并以这个数字为起点,构造一个等差数列,如果输入数字n是奇数,等差数列之间的差为3;如果n是偶数,则等差数列之间的差是4。从n开始,连续加100项,求这100项之和。

2.用递归函数求20的阶乘,注意讲解阶乘的数学意义。

3.巴什游戏,一堆石子有n个,两人轮流取子,规定每人每次至少取一个,最多取m个(m<n),最后取完石子的人获胜,请问应该如何取子?