ユークリッドの互除法とは
ユークリッドの互除法 (Euclidean algorithm)は、2つの自然数(または整数)の最大公約数(Greatest Common Divisor, GCD)を効率的に求めるためのアルゴリズムです。紀元前300年頃に古代ギリシャの数学者ユークリッドが著した数学書『原論』(Elements)の第7巻に記載されており、「人類最古のアルゴリズム」の一つとしても広く知られています。
最大公約数を求める最も素朴な方法は、2つの数をそれぞれ素因数分解し、共通する素因数を掛け合わせることですが、数が大きくなると素因数分解そのものの計算量が膨大になり、現実的な時間で解くことが困難になります。一方、 ユークリッドの互除法 を用いれば、数千桁に及ぶ巨大な数同士であっても、極めて高速に最大公約数を計算することが可能です。
基本的な定理と仕組み
2つの自然数 $a$ と $b$ ($a \ge b$)の最大公約数を $\gcd(a, b)$ と表します。 ユークリッドの互除法は、以下の単純な定理に基づいています。
$$ a = bq + r \implies \gcd(a, b) = \gcd(b, r) $$つまり、「 $a$ を $b$ で割ったときの商を $q$ 、余りを $r$ としたとき、 $a$ と $b$ の最大公約数は、 $b$ と $r$ の最大公約数に等しい」という性質です。
定理の証明
なぜ $\gcd(a, b) = \gcd(b, r)$ が成り立つのでしょうか。簡単に証明してみましょう。
- $a$ と $b$ の任意の公約数を $d$ とします。このとき、 $a = md, b = nd$ ($m, n$ は整数)と表せます。
- $a = bq + r$ より、 $r = a - bq$ となります。
- これに代入すると、 $r = md - (nd)q = d(m - nq)$ となります。
- $m - nq$ は整数なので、 $d$ は $r$ の約数でもあります。よって、 $a$ と $b$ の公約数 $d$ は、 $b$ と $r$ の公約数でもあります。
- 逆に、 $b$ と $r$ の公約数を $e$ とすると、 $b = k e, r = l e$ と表せます。
- $a = bq + r = (k e)q + l e = e(kq + l)$ となり、 $e$ は $a$ の約数になります。よって、 $b$ と $r$ の公約数 $e$ は、 $a$ と $b$ の公約数でもあります。
- したがって、 $\{a, b\}$ の公約数の集合と $\{b, r\}$ の公約数の集合は完全に一致し、その最大値である最大公約数も等しくなります。 $\blacksquare$
アルゴリズムのフローチャート
この性質を利用し、余りが $0$ になるまで割り算を繰り返すのがユークリッドの互除法です。
flowchart TD
Start["開始: a, b を入力"] --> Check{"b == 0 ?"}
Check -- "Yes" --> End["GCD は a"]
Check -- "No" --> Calc["r = a % b"]
Calc --> Update["a = b, b = r"]
Update --> Check
具体例での計算プロセス
例として、 $a = 1071$ と $b = 1029$ の最大公約数を求めてみましょう。
- $1071 \div 1029 = 1 \cdots 42$ ($a=1029, b=42$ に更新)
- $1029 \div 42 = 24 \cdots 21$ ($a=42, b=21$ に更新)
- $42 \div 21 = 2 \cdots 0$ (余りが $0$ になったので終了)
最後に割る数として残った $21$ が、 $1071$ と $1029$ の最大公約数です。
プログラムによる実装
Pythonでの実装
Pythonでは、再帰関数を用いる方法と、 while ループを用いる方法があります。ループを用いる方が関数呼び出しのオーバーヘッドがなく高速です。
| |
C++での実装
C++17以降では <numeric> ヘッダに std::gcd が標準実装されていますが、自作する場合は以下のようになります。
| |
計算量とラメの定理
ユークリッドの互除法はどれくらい速いのでしょうか。この計算量については、1844年にフランスの数学者ガブリエル・ラメによって証明された ラメの定理 (Lamé’s theorem)が有名です。
ラメの定理 2つの自然数 $a, b$ ($a > b$)に対してユークリッドの互除法を適用したときの割り算の回数は、 $b$ の十進法での桁数の $5$ 倍以下である。
これにより、アルゴリズムの時間計算量は $O(\log(\min(a, b)))$ となります。
最悪のケース(最も割り算の回数が多くなるケース)は、フィボナッチ数列の隣り合う2項を与えた場合です。例えば、 $F_{n+2}$ と $F_{n+1}$ の最大公約数を求める過程は、常に商が $1$ になり、次々に小さいフィボナッチ数へと移行していきます。
拡張ユークリッドの互除法
最大公約数を求めるだけでなく、以下のベズーの等式(Bézout’s identity)を満たす整数 $x, y$ を求めるアルゴリズムに拡張したものを、 拡張ユークリッドの互除法 (Extended Euclidean algorithm)と呼びます。
$$ ax + by = \gcd(a, b) $$拡張ユークリッドの互除法の実装
再帰呼び出しから戻る過程で、 $x, y$ の係数を逆算していきます。
| |
現代社会における応用(RSA暗号など)
拡張ユークリッドの互除法は、単なる数学パズルではなく、現代のインターネット社会を支える不可欠な技術です。 代表的な例が RSA暗号 です。RSA暗号の鍵生成プロセスでは、ある数 $e$ と、オイラー関数 $\phi(N)$ に対して、 $e d \equiv 1 \pmod{\phi(N)}$ を満たす秘密鍵 $d$ (モジュラ逆元)を求める必要があります。 これは $ed + k\phi(N) = 1$ という形に変形できるため、まさに拡張ユークリッドの互除法を用いて高速に $d$ を計算することができます。
まとめ
ユークリッドの互除法は、紀元前という遥か昔に発見されながら、その無駄のないロジックと計算効率の高さから、現代のコンピューターサイエンスの根幹を支え続けています。アルゴリズムを学ぶ上で最初に触れることが多いテーマですが、その背後には数学的な美しさと実用性が詰まっています。
