原文地址:https://hbfs.wordpress.com/2013/12/10/the-speed-of-gcd/
最大公约数求法
- 最朴素的方法:如果一个是0,返回另一个;否则,分别找到a,b所有的约数,找公共最大的约数
- 欧几里得辗转法:最原始提出的方法应该是辗转相减,然后改进为辗转相除(取模)
- 二进制gcd:这主要是因为gcd有以下3个特点:
- 如果a,b都是偶数,那么gcd(a,b) = gcd(a/2,b/2)*2
- 如果a是奇数,b是偶数,那么gcd(a,b) = gcd(a,b/2)
- 如果a,b都是奇数,那么gcd(a,b) = gcd((a-b)/2,b)
代码实现
代码实现了辗转相除的递归、迭代版本,还有二进制gcd的基本版、迭代复合版、使用内建函数版。
template <typename T>
T gcd_recursive( T a, T b ) {
return b == 0 ? a : gcd_recursive( b, a % b );
}
template <typename T>
T gcd_iterative2( T a, T b ) {
T t;
while( b != 0 ) {
t = a % b;
a = b;
b = t;
}
return a;
}
template <typename T>
T gcd_iterative( T a, T b ) {
while( a != 0 ) {
b %= a;
if( b == 0 ) return a;
a %= b;
}
return b;
}
template <typename T>
T gcd_binary( T a, T b ) {
if( a == 0 ) return b;
if( b == 0 ) return a;
int k = 0;
while( ((a | b) & 1) == 0 ) {
a >>= 1;
b >>= 1;
++k;
}
while( (a & 1) == 0 ) a >>= 1;
do {
while( (b & 1) == 0 ) b >>= 1;
if( a > b ) {
T t = a;
a = b;
b = t;
}
b -= a;
} while( b != 0 );
return a << k;
}
// __builtin_ctz返回一个整数末尾0的个数,count trailing zero
template <typename T>
T gcd_binary_builtin( T a, T b ) {
if( a == 0 ) return b;
if( b == 0 ) return a;
int k = __builtin_ctz( a | b );
a >>= __builtin_ctz( a );
do {
b >>= __builtin_ctz( b );
if( a > b ) {
T t = a;
a = b;
b = t;
}
b -= a;
} while( b != 0 );
return a << k;
}
template <typename T>
T gcd_binary_recur( T a, T b ) {
if( a == 0 ) return b;
if( b == 0 ) return a;
int k = 0;
while( ((a | b) & 1) == 0 ) {
a >>= 1;
b >>= 1;
++k;
}
return gcd_recursive( a, b ) << k;
}
运行结果
随机生成5000000对整数,然后调用gcd的不同版本,生成过程大概是0.08s,所以对整体时间影响可以忽略。
函数 | 时间 | 备注 |
---|---|---|
gcd_recursive | 0m1.078s | 意外的是,它比迭代版本快一点 |
gcd_iterative/gcd_iterative2 | 0m1.098s | gcd_iterative比gcd_iterative2稍快,可能跟展开有关 |
gcd_binary | 0m1.523s | 因为每次只能移一位,所以速度慢 |
gcd_binary_recur | 0m1.092s | 还不如单纯递归版本 |
gcd_binary_builtin | 0m0.821s | 这个是最快的 |
其实二进制gcd计算次数会更多一点,但是它执行的都是适合计算机的二进制位操作,而不是耗时的除法操作,但是基本版本移位操作太多,所以表现很差,内建函数版本充分利用了cpu指令,所以是速度最快的。
参考
[1] https://hbfs.wordpress.com/2013/12/10/the-speed-of-gcd/