题目来自编程之美
题目
注意:这里不考虑负数。
思路(1)辗转相除法
思想:
参数要求:无要求,x >= y 或 y >= x均可
gcd(x,y) = gcd(y ,x % y);
原理:
假设 x = ky + b,其中 k = x/y,b = x%y.
则,b = x - ky
假设 整数t同时整除x和y,则
式子两边同时除以t,得
b / t = (x - ky) / t = 整数
则,b = x%y 也能整除t。
即,x 和 y的最大公约数 等于 y和 x%y 的最大公约数。
注意,这里我们只考虑正数,即有x > y。
此时,就有了 gcd(x,y) = gcd(y ,x % y),y > 0;
在式子中,右边的参数参数都在减小,即把原问题转换为求两个更小数的公约数了,一直到其中一个数为0,则另外一个数就为其最大公约数。
举例:
30 和 12 的最大公约数 等于 12 和 6 的最大公约数。
12 和 6 的最大公约数 等于 6 和 0 的最大公约数。
6 和 0 的最大公约数 等于 6,则30 和 12 的最大公约数 等于 6。
特点:计算次数比较少,但是取模运算运算代价昂贵,尤其是针对大数据。
代码:
int Gcd(int x,int y)
{
return y == 0 ? x : Gcd(y,x % y);
}
代码注意事项:
(1)传参时,不需要要求x > y。
(2)需要注意程序的形参(x,y)和入口参数的顺序(y,x%y)。
思路(2)两数相减法
思想:
参数要求:有要求,x >= y
gcd(x,y) = gcd(y,x - y);
举例:
<1> 30 和 12 的最大公约数 等于 12 和 18 的最大公约数。
<2> 18 和 12 的最大公约数 等于 12 和 6 的最大公约数。
<3> 12 和6 的最大公约数 等于 6 和 0 的最大公约数。
<4> 6 和 0 的最大公约数 等于 6,则30 和 12 的最大公约数 等于 6。
特点:计算次数比较多,但是不需要取模运算。
代码:
int Gcd(int x,int y)
{
if (y > x)
{
return Gcd(y,x);/*要求x > y*/
}
return y == 0 ? x : Gcd(y, x - y);
}
代码注意事项:
(1)传参时,要求x > y。
(2)在程序执行过程中,也有可能出现 x < y 的情况(如举例中第一次计算就出现了),因此需要在程序中写交换语句。
思路(3)辗转相除法与相减结合
引入的原因:结合两种方法,使得迭代次数少 且 又没有取模等代价昂贵的运算,继而快速求解大数据的最大公约数。
思路:
运用取模运算的性质:
对于 x 和 y 来说,
(1)如果x = k * x1 且 y = k * y1,则x 和 y的最大公约数等于 k * (x1 和 y1的最大公约数)。
(2)如果x = p * x1 且 p 素数 且 y % p != 0,则x 和 y的最大公约数等于x1 和 y的最大公约数。
在程序中,需要寻找一个类似(2)的p,即需要判断p是否是x或者y的因子。
由于在程序中判断2是否是一个数的因子还是比较方便的,这里就令p = 2。
为啥是方便的呢?
判断2是否为x的因子,即判断x是否为偶数。可以直接使用位运算,来判断x是否为偶数,很方便。
这样,我们可以通过除2来减少计算次数。
具体思路:
如果 x为偶,y为偶,则gcd(x,y) =2 * gcd(x >> 1,y >> 1)。
如果 x为偶,y为奇,则gcd(x,y) = gcd(x >> 1,y)。
如果 x为奇,x为偶,则gcd(x,y) = gcd(x,y >> 1)。
如果 x为奇,x为奇,则gcd(x,y) = gcd(y,x - y)。
在执行gcd(y,x - y)后,这俩参数一个为奇数,一个为偶数,下一步就又有除2操作了。
注意:当x和y均为偶数时,别忘了乘以2。
分析:
为啥呢:如果x为2的幂且y = 3,则可以一直除2,一直到最后。
举例:
<1> 30 和 12 的最大公约数 等于 15 和 6 的最大公约数 * 2。
<2> 15 和 6 的最大公约数 等于 15 和 3 的最大公约数。
<3> 15 和 3 的最大公约数 等于 3 和 12 的最大公约数。
<4> 12 和 3 的最大公约数 等于 6 和 3 的最大公约数。
<5> 6 和 3 的最大公约数 等于 3 和 3 的最大公约数。
<6> 3 和 3 的最大公约数 等于 3 和 0 的最大公约数。
<7> 3 和 0 的最大公约数 等于 3,则30 和 12 的最大公约数 等于 6。
注意:别忘了乘以2.
代码:
int Gcd(int x,int y)
{
if (x < y)
{
return Gcd(y,x);/*形参要求 x > y */
}
if (y == 0)
{
return x;
}
if ((x & 1) == 0) //x为偶数
{
if ((y & 1) == 0) //x为偶数,y为偶数
{
return Gcd(x >> 1,y >> 1) << 1;//别忘了乘以2
}
else //x为偶数,y为奇数
{
return Gcd(x >> 1,y);
}
}
else
{
if ((y & 1) == 0) //x为奇数,y为偶数
{
return Gcd(x,y >> 1);
}
else //x为奇数,y为奇数
{
return Gcd(y,x - y);
}
}
}
代码注意事项:
(1)传参时,要求x > y。
(2)在程序执行过程中,也有可能出现 x < y 的情况,因此需要在程序中写交换语句。