Pythonで学ぶユークリッドの互除法

    このアルゴリズムの解説に自然数AとBの最大公約数を求める問題を扱います。 単純な方法で計算すると時間がかかってしまいます。 しかし、 ユークリッドの互除法を使うと、計算量O (log (A+B)) で答えが求められます。 アルゴリズムの紹介に加え、計算量にlogが出てくる理由を解説します。

    目次

    最大公約数・最小公倍数

    2つ以上の正の整数に共通する約数 (公約数) の中で最大のものを最大公約数といいます。たとえば6と9の最大公約数は3です。 これは6の約数が 「1,2,3,6」 9の約数が 「1,3,9」 であり、共通する最大の数は3であるからです。

    一方、2つ以上の正の整数に共通する倍数 (公倍数) の中で最小のものを最小公倍数といいます。たとえば6と9の最小公倍数は18です。 これは6の倍数が 「6,12,18,24,309の倍数が 「9, 18, 27, 36,45,…」 と続き、 共通する最小の数は18であるからです。

    2つの正の整数a,bには以下の性質が成り立つため、最大公約数と最小公倍数のうち片方でも分かれば、もう片方は簡単に計算することができます。

    a x b = (aとbの最大公約数)×(aとbの最小公倍数)

    そして最大公約数はユークリッドの互除法により効率的に計算することができます 

    単純なアルゴリズム

    まず、33と88の最大公約数を計算してみましょう。明らかに答えは33以下です。そこで以下のように「1,2,…,33それぞれについて、33と88両方が割り切れるかどうか」を調べる方法が考えられます。しかし、手計算では答えを求めるのに時間がかかってしまいます。 

    最大公約数を求めるプログラム

    一般の正の整数 A,Bの最大公約数についても、同じように1からmin (A,B) まで割り切れるかどうかを調べることで求められます。 たとえば次のコードのような実装が考えられます。 しかし余りの計算を 2 × min (A, B) 回行う必要があり、 あまり効率的ではありません。

    # 正の整数 A と B の最大公約数を返す関数
    
    # GCD は Greatest Common Divisor(最大公約数)の略
    
        def GCD(A, B):
    
        answer = 0
    
        for i in range(1, min(A, B) + 1):
    
        if A % i == 0 and B % i == 0:
    
            answer = i
    
        return answer
    
    A, B = map(int, input().split())
    
    print(GCD(A, B))

    効率的なアルゴリズム:ユークリッドの互除法

    実は、以下の方法を使うと、 2つの数の最大公約数を高速に計算することができます。

    たとえばこの方法で33と88の最大公約数 123と777の最大公約数をそれぞれ計算すると、計算過程は以下のようになります。 全部確かめる方法に比べ、計算回数が大幅に少ないです。

    STEP
    1. 大きいほうの数を「大きいほうを小さいほうで割った余り」に書き換えるという操作を繰り返す
    STEP
    2.片方が0になったら操作を終了する。もう片方の数が最大公約数である

    ユークリッドの互除法の実装例

    このようなアルゴリズムをユークリッドの互除法といいます。 AとBの最大公約数を求めるときの計算量はO(log (A+B)) であるため、 A, B が 1018程度であっても一瞬で計算できます。

    実装の一例としてコード3.2.2が考えられます。 AとBの大小関係によって行うべき操作が変わるので、

    if文を用いて場合分けを行っています。 なお、 再帰関数 (3.6節) を用いた賢い実装方法もあります。

    # 正の整数 A と B の最大公約数を返す関数

    # GCD は Greatest Common Divisor(最大公約数)の略

    def GCD(A, B):
    
        while A >= 1 and B >= 1:
    
            if A < B:
    
                   B = B % A  # A < B の場合、大きい方 B を書き換える
    
            else:
    
                   A = A % B  # A >= B の場合、大きい方 A を書き換える
    
        if A >= 1:
    
            return A
    
        returnB
    
    A, B = map(int, input().split())
    
    print(GCD(A, B))

    計算回数が log になる理由

    次に、計算量がO (log (A+B)) になる理由を考えてみましょう。

    まず、「1回の操作でA+Bの値が必ず2/3倍以下に減る」という重要な事実があります。たとえばA=33,B=88の場合の計算過程は次の通りであり、確かに2/3倍以下に減っています。

     

    この事実はなぜ成り立つのでしょうか。 A<Bの場合はA,Bを逆にすれば良いので、ここではA≧Bの場合のみ考えます。 

    実は、 AとBの差が2倍以上かどうかによって場合分けを行うと、以下の理由により両方2/3倍以下に減っていることが分かります。

    ・差が2倍未満: 操作によってA+Bの値は 「3B未満」 から Bだけ減る

    ・差が2倍以上: 操作によってA+Bの値は 「3B以上」から「2B未満」に減る

    たとえばBの値を10に固定して考えた場合は以下の通りです。 Aの値が20未満の場合も、 20以上の場合も、必ず2/3倍以下に減っています。

    3個以上の最大公約数

    3個以上の数の最大公約数も、ユークリッドの互除法によって計算することができます。 具体的なアルゴリズムの流れは次のようになります。

    ・まず、 1個目の数と2個目の数の最大公約数を計算する

    ・次に、前の計算結果と3個目の数の最大公約数を計算する

    ・次に、前の計算結果と4個目の数の最大公約数を計算する

    ……前の計算結果とN個目の数の最大公約数を計算する (この結果が答え)

    たとえば24,40,60,80, 90, 120の最大公約数の計算過程は以下のようになります。なお、3項以上のビット演算と同様、計算順序を入れ替えても計算結果は変わりません。 

    よかったらシェアしてね!
    • URLをコピーしました!
    目次