| tags |
|
|
|---|---|---|
| e_maxx_link | euclid_algorithm |
Given two non-negative integers
(here the symbol "$\mid$" denotes divisibility, i.e. "$k \mid a$" means "$k$ divides
When one of the numbers is zero, while the other is non-zero, their greatest common divisor, by definition, is the second number. When both numbers are zero, their greatest common divisor is undefined (it can be any arbitrarily large number), but it is convenient to define it as zero as well to preserve the associativity of
The Euclidean algorithm, discussed below, allows to find the greatest common divisor of two numbers
The algorithm was first described in Euclid's "Elements" (circa 300 BC), but it is possible that the algorithm has even earlier origins.
Originally, the Euclidean algorithm was formulated as follows: subtract the smaller number from the larger one until one of the numbers is zero. Indeed, if
Note that
int gcd (int a, int b) {
if (b == 0)
return a;
else
return gcd (b, a % b);
}Using the ternary operator in C++, we can write it as a one-liner.
int gcd (int a, int b) {
return b ? gcd (b, a % b) : a;
}And finally, here is a non-recursive implementation:
int gcd (int a, int b) {
while (b) {
a %= b;
swap(a, b);
}
return a;
}Note that since C++17, gcd is implemented as a standard function in C++.
The running time of the algorithm is estimated by Lamé's theorem, which establishes a surprising connection between the Euclidean algorithm and the Fibonacci sequence:
If
Moreover, it is possible to show that the upper bound of this theorem is optimal. When
Given that Fibonacci numbers grow exponentially, we get that the Euclidean algorithm works in
Another way to estimate the complexity is to notice that
Calculating the least common multiple (commonly denoted LCM) can be reduced to calculating the GCD with the following simple formula:
Thus, LCM can be calculated using the Euclidean algorithm with the same time complexity:
A possible implementation, that cleverly avoids integer overflows by first dividing
int lcm (int a, int b) {
return a / gcd(a, b) * b;
}The Binary GCD algorithm is an optimization to the normal Euclidean algorithm.
The slow part of the normal algorithm are the modulo operations. Modulo operations, although we see them as
It turns out, that you can design a fast GCD algorithm that avoids modulo operations. It's based on a few properties:
- If both numbers are even, then we can factor out a two of both and compute the GCD of the remaining numbers:
$\gcd(2a, 2b) = 2 \gcd(a, b)$ . - If one of the numbers is even and the other one is odd, then we can remove the factor 2 from the even one:
$\gcd(2a, b) = \gcd(a, b)$ if$b$ is odd. - If both numbers are odd, then subtracting one number of the other one will not change the GCD:
$\gcd(a, b) = \gcd(b, a-b)$
Using only these properties, and some fast bitwise functions from GCC, we can implement a fast version:
int gcd(int a, int b) {
if (!a || !b)
return a | b;
unsigned shift = __builtin_ctz(a | b);
a >>= __builtin_ctz(a);
do {
b >>= __builtin_ctz(b);
if (a > b)
swap(a, b);
b -= a;
} while (b);
return a << shift;
}Notice, that such an optimization is usually not necessary, and most programming languages already have a GCD function in their standard libraries.
E.g. C++17 has such a function std::gcd in the numeric header.