昨天晚上睡觉前看到CSDN上一个老师出的一道题,求一个算法求两个很大的数字(n_above,n_below)的最大公约数。
对于一般的数想要求最大公约数,一般的算法应该是先找小的那个数n_below的所有约数放在数组里,(从2除到(n_below-1)/2),然后从大到小去试能不能被n_above整除,第一个能整除的那个数就是最大公约数。
但是这样时间空间复杂度都较大,改进一点的是每发现一个n_below的约数都用它去除一下n_above,这样只要用两个变量n_below_div,n_above_div分别保存当前两数分别的最大约数,当除完(n_below-1)/2后,n_above_div就是两数最大公约数。
可是这种方法对于超大的数还是太笨了,例如题目给出的例子有上十位,这样起码也要计算2*(10^10)/2次。
当时就要睡觉了,所以就关机了,然后我就躺在床上想。开始的想法是,如果一个数除不尽,那必定它的倍数也除不尽,例如从2开始,如果2除不尽,那么接下来所有的偶数都不用除了,如果能除尽,那么小于n_below/4时,只要算偶数就行了(因为这个范围内如果有奇数的约数,它的二倍(小于n_below/2)也一定是其约数)。
但是又想,这么也只不过最多减少一半的时间复杂度,即使算上3的倍数的话,最多也只减少2/3,对这么超大的数字绝对杯水车薪。
然后我突然想到了一个算法。我们知道一定有等式n_above=i*n_below+n_above(mod n_below),这么一写就明朗了,如果两个数能被同一个数,假设div,整除,那么div一定能整除n_above(mod n_below),即(n_above(mod n_below))(mod div)==0,用语言说就是两个数的最大公约数等于大数对小数取模后跟小数的最大公约数。关键的是这么个等式还能递归下去。
这样就一下子天晴了,以前我从未想过%这个运算符有这么好的作用,现在知道模运算多么的奇妙,就这么来回模下去,两个巨大的数瞬间就被模小了。但是这个时间复杂度到底是多少呢,这个我还没算出来,因为他不是线性的,如果被取模的数接近取模的数,那取模后的数会非常小;而如果被取模的数如果很小,那取模后的数又会很大。
这么一来就剩一个问题,这么循环取模,什么时候退出呢,显然是找到了最大公约数的时候,一种情况是为1,就是原来两数互质,那么这个时候x mod y==1,还有一种就是x mod y==0,y即为最大公约数。那么代码就很好写了:
#include<iostream>
using namespace std;
int main()
{
int x,y;
int mod;
int div;
cin>>x>>y;
if(x<y)
{int temp=x;x=y;y=temp;}
do
{
mod=x%y;
x=y;
y=mod;
}while(mod!=0 && mod!=1);
if(mod==0) div=x;
else if(mod==1) div=1;
cout<<div;
return 0;
}
因为省事数据类型全用了整形,也没有对输入为负数进行优化