从欧几里得开始(一)

从欧几里得开始(一)

如何计算两个自然数的最大公约数

公约数有时又叫做公共因子(common factor), 我们从小就有很先进的一个理论作为常识:任何一个自然数都有可以拆成一些因子相乘得到,更进一步,如果我们将素数作为因子集合,那么这个拆分的形式也是唯一的。换一句话来说,一个个体能够被分解,另外一个个体也能被分解,你可以找到一些公共部分,更进一步说,如果公共部分限定在一个特定的集合里面,这个拆分是唯一的。你不觉得这是一件十分先进的理论吗?

公约数就是这样一个理论的砖块,那么如何去找公约数?

找到一个自然数的约数

对于一个自然数而言,什么样的数是约数(因子)?因为是把自然数进行乘法分解,那么自然数对于约数的断定就需要乘法参与其中。

约数(或者说因子)不能超过自然数本身,因为我们最开始的目的是实现自然数的拆分,将自然数拆成更小一些的部分,所以如果我们的约数超过了本体的自然数,我们就不需要进行拆分这一行为了,这是说约数要受限,有界,限制于自然数。

这两段论述说明了一个约数的三个性质:

\[\begin{align*} &1,约数a是一个自然数;&&&\\ &2,自然数N,约数a,能够找到正整数k,使得ak=N成立;&&&\\ &3,约数a,自然数N,满足a\leq N;&&&\\ \end{align*} \]

如果不想扩展到负约数,我们还要加一条:

\[\begin{align*} 4,约数a\geq1;\\ \end{align*} \]

所以对于一个具体的自然数,比如说28的约数是哪一些;

首先对于自然数28来说约数的范围是在1到28之间,因为性质3和4;

然后这些约数是自然数(性质1),所以一个自然的想法是列举1到28之间的所有自然数。

1,2,3,4,5,6,7,8,9,... 27,28,一个一个的去验证,这些自然数是不是约数;

那么这个裁决或者判定依靠性质2,对于约数\(a\),自然数\(N\)能否找到正整数k,满足$ ak=N$.

就是说要有一个这样的过程:

\(a=1,N=28\)时,是否找到一个正整数k,让 \(ak=N\)成立,就是\(1\cdot k=28\)成立;

我们

让k=1开始,\(1\cdot 1=28\)成立吗?不成立;下一个

k=2,\(1\cdot 2=28\)成立吗?不成立;下一个

k=3,\(1\cdot 3=28\)成立吗?不成立;下一个

k=4,\(1\cdot 4=28\)成立吗?不成立;下一个

k=5,\(1\cdot 5=28\)成立吗?不成立;下一个

k=6,\(1\cdot 6=28\)成立吗?不成立;下一个

.....直到

k=28,\(1\cdot 28=28\)成立吗?成立,恩找到这个这个k了;

我们做了28次验算,推断出了1是自然数28的约数

\(a=2,N=28\)这个组合,也有一个类似的过程:

我们

让k=1开始,\(2\cdot 1=28\)成立吗?不成立;下一个

k=2, \(2\cdot 2=28\)成立吗?不成立;下一个

k=3, \(2\cdot 3=28\)成立吗?不成立;下一个

k=4, \(2\cdot 4=28\)成立吗?不成立;下一个

k=5, \(2\cdot 5=28\)成立吗?不成立;下一个

....

k=14, \(2\cdot 14=28\)成立吗?成立;

这一次14次验算,我们找到了k,得出了一个结论:

2是28的约数

现在轮到\(a=3,N=28\)这个组合,也有这样一个类似的过程:

我们

让k=1开始, \(3\cdot 1=28\)成立吗?不成立;下一个

k=2, \(3\cdot 2=28\)成立吗?不成立;下一个

k=3, \(3\cdot 3=28\)成立吗?不成立;下一个

k=4, \(3\cdot 4=28\)成立吗?不成立;下一个

k=5, \(3\cdot 5=28\)成立吗?不成立;下一个

k=6, \(3\cdot 6=28\)成立吗?不成立;下一个

k=7, \(3\cdot 7=28\)成立吗?不成立;下一个

k=8, \(3\cdot 8=28\)成立吗?不成立;下一个


k=9, \(3\cdot 9=28\)成立吗?不成立;下一个

k=10, \(3\cdot 10=28\)成立吗?不成立;下一个

k=11, \(3\cdot 11=28\)成立吗?不成立;下一个

.....

k=28, \(3\cdot 28=28\)成立吗?不成立;下一个

k=29, \(3\cdot 29=28\)成立吗?不成立;下一个

k=30, \(3\cdot 30=28\)成立吗?不成立;下一个

k=31, \(3\cdot 31=28\)成立吗?不成立;下一个

k=32, \(3\cdot 32=28\)成立吗?不成立;下一个

k=33, \(3\cdot 33=28\)成立吗?不成立;下一个

....

k=100, \(3\cdot 100=28\)成立吗?不成立;下一个

k=101, \(3\cdot 101=28\)成立吗?不成立;下一个

k=102, \(3\cdot 102=28\)成立吗?不成立;下一个

....

k=10001, \(3\cdot 10001=28\)成立吗?不成立;下一个

k=10002, \(3\cdot 10002=28\)成立吗?不成立;下一个

....

k=10000001, \(3\cdot 10000001=28\)成立吗?不成立;下一个

k=10000002, \(3\cdot 10000002=28\)成立吗?不成立;下一个

...

这个过程找不到,这样的正整数k,我们能轻易的给下这个判断吗,毕竟验算了千万次级别了,还是没找到k符合。

整个过程中,我们从标红的两个判断来看:


k=9, \(3\cdot 9=28\)成立吗?不成立;下一个

k=10, \(3\cdot 10=28\)成立吗?不成立;下一个

在k=9时,\(3\cdot 9=27 < 28\);

在k=10时,\(3\cdot 10=30 > 28;\)

这9和10就像是形成了一道边界,

从k=10开始,可以用归纳证明:

\(k\ge10时,3k>28\)

这个结论\(k\ge10时,3k>28\)一次性的排除了我们需要考虑的无限多种情况,这就是归纳法的作用,把无限的情况给刚掉了。

所以9这个数就是我们的边界,而且是最好的边界,因为这个数限定的验算情况,按照直觉来说,是这个情况下最少的;

当时我们先不需要这么好的一个边界,我们只要一个粗略的边界,比如28本身,也就是N本身这个边界,将无数多种情况的验算,变成28种,对于28这个具体的自然数来说是28种,对于N这个自然数来说,就是N种。

所以考察一个数a是否是自然数N的约数,只要最多进行N种验算,就能确定结果。这样就不会出现a=3是那种无限的验证情况,这要归功于归纳法,就是这个结论\(k\ge10时,3k>28\);它把无限多种情况给变成有限情况。

综合以上,要断定一个数a是否是自然数N的约数被总结出一个下面的过程:

\[\begin{align*} &1,有两个输入,数a,数N;\\ &2,一个迭代(循环)过程:k=1一直迭代到k=N;\\ &3,一个判定过程:ak=N?\\ &4,两个结果,true(真)和false(假);\\ \end{align*} \]

以上是原材料,现在开始按照一定顺序组合这些:

\[\begin{align*} &步骤1,(迭代过程开始)k=1;\\ &步骤2,(判定过程)ak=N?判定通过,返回true;不通过,接着进行步骤3; \\ &步骤3,(迭代过程是否结束,就是k是否到达N+1,这个就是边界起作用的地方)\\ & 进行这个判定k=N+1?判定通过,返回false;\\ & 否则k=k+1(未达边界进行下一次迭代),回到步骤2 ;\\ \end{align*} \]

步骤1-步骤3组合出来的这个过程需要两个输入的数,总会产生一种结果,要么是true,要么是false。这样一个的过程叫做一个算法。

这里让人有些意外的是,步骤3里面出现的判定,k=N+1? 这个判定在第一时间并不会正常想到,但是稍微分析一下,这就是边界条件发挥重要作用的地方,这也是算法会终止最后的到一个结果的关键,从无限情况收束到有限的关键。

边界,这是一个很关键的词。

嗯,文字的描述十分不直观,如果用图,这个算法就是这样表示:

1

以简单形式的这个算法完成了,但是我们知道,这必定不是一个很高效的算法。

比如我们之前已经充分验算过的情况来说:

\(a=1,N=28\)作为这个算法的输入,我们做一个简单的分析,(只考虑判定过程\(ak=N?\)到底运转了几次?

(在这个算法里面,迭代过程以及另外一个判定过程\(k=N+1?\)跟判定过程\(ak=N?\)运转次数是绑在一起的,这三个过程基本上是连续发生,唯一特殊的情况出现在抵达边界的时候,三个过程中迭代会少一次。但是我们不需要这么详尽的分析,因为三个差不多,统计一个就知道另外两个的情况了。)

k=1,第1次,下一轮迭代;

k=2,第2次,下一轮迭代;

......,

k=28,第28次,不用迭代了,出结果了,true。

嗯,我们用了28次判定过程\(ak=N?\)

现在换成另外一个输入:

\(a=2,N=28\),类似k=14出结果,用了14次判定过程\(ak=N?\)

\(a=3,N=28\), 这里需要注意,k=10开始其实已经没有必要进行下去了,但是,对于这个算法来说,还没有终结;知道k=29时,这个算法才结束,得到false值,这一共运转了29次判定过程\(ak=N?\)

最好的情况发生在\(a=28,N=28\),只用了1次判定;

最糟糕的情况就是出false的时候,29次判定;

所以这个算法的执行效率十分依赖输入。

我们来考虑一个情况:

29是不是28的约数?

\(a=29,N=28\)按照这个算法来说,也需要29次判定。这不太有效率。

而且,我们认定了一个自然数的约数满足\(a\le N\);这样一个前提,这个前提在我们的算法里面还没有体现出来,那么用这个条件加一个判定过程\(a\le N\)?就加在初始输入之后:

这一改进,把所有类似

\(a=29,N=28;\)

\(a=30,N=28\)

给缩减到1次判定,而不是之前的29次。

就是说算法可以通过增加适当判定过程来改变运行性能,我们实验出了这样一个结论,虽然没有严格的证明,但是直觉上来说,这是可行的。

之前我们提到了对于:

\(a=3,N=28\), k=9是最好的边界,超过9的k被归纳法给终结掉了,所以,这个9是怎么计算出来的,28/3得到的最大整数就是9,如果有整除运算,直接28整除以3就可以得到了,如果不支持整除运算,用28/3的结果向下取整,也可以得到9。

向下取整的符号一般用去掉头的方括号,\(\lfloor a \rfloor\)表示a向下取整。

那么我们现在对于这个最好的边界就可以用这样的符号记录:$ \lfloor \frac{N}{a} \rfloor$

现在我们将算法的图再修改一下:

分,第二次是绿色部分。

第二次的修改影响的是得出false的结果,从之前需要重复判定的N+1次,降到$ \lfloor \frac{N}{a} \rfloor$次。

比如对于,\(a=3,N=28\),从29次降到了9次。

类似,对于\(a=13,N=28\),从29次降到了2次。

同样,我们观察一些特殊的情况:

\(a=15,N=28\), 1次判定,结果false;

\(a=16,N=28\), 1次判定,结果false;

\(a=17,N=28, 1次判定,结果false;\)

\(a=18,N=28, 1次判定,结果false;\)

\(a=19,N=28, 1次判定,结果false;\)

....

\(a=26,N=28\), 1次判定,结果false;

\(a=27,N=28, 1次判定,结果false;\)

这些情况有什么特殊性,因为依靠这个算法,只需要1次判定,结果就出来了,而且结果是false。

仔细观察这些情况特别是以为\(a\)都超过了自然数\(N\)的一半。那么我们总结出一个结论,超过自然数一半的数(除开这个自然数本身之外)不可能会是这个自然数的约束。

也就是所约数a,自然数N,要满足\(a\leq \frac {N}{2}\),更准确说法是\(a\neq N时a\leq \lfloor\frac {N}{2}\rfloor\)

我们可以依靠这个,消除我们算法里面冗余的步骤;

现在这个条件被我们应用到我们的算法图里面:

这次修改的是蓝色部分的内容,这个作用直接大于自然数一半以上的约数,只用一次判定就结束了,不需要走过我们的迭代过程,但是实际上如果用了\(k= \lfloor \frac{N}{a} \rfloor\)?这个修改后的判定过程,消除的只是\(ak=N?\)这一过程的计算,省了这一步,但是又多了另外一个判定过程\(a=N?\)没有多大的改进。

这样经过几次对于判定过程的修改,其中,最重要的是发现了新的边界,然后才想到修改判定过程,所以怎么发现新的边界是对于一个算法进行改善的重要思路

嗯,代码实现,我最近在用java,等下补充一个java实现。

欧几里得的算法,还没有开始,但是判定约数的算法就是上面的过程。之后再补。

posted @ 2021-01-13 19:55  Rivenbar  阅读(96)  评论(0)    收藏  举报