ユークリッドのアルゴリズムの時間の複雑さ


97

ユークリッドの最大の共通分母アルゴリズムの時間の複雑さを判断するのが困難です。この擬似コードのアルゴリズムは次のとおりです。

function gcd(a, b)
    while b ≠ 0
       t := b
       b := a mod b
       a := t
    return a

abに依存しているようです。私の考えでは、時間の複雑さはO(a%b)です。あれは正しいですか?それを書くより良い方法はありますか?


14
Knuth TAOCP、Volume 2を参照してください-彼は広範囲をカバーしています。ちょうどFWIW、ちょっとしたヒント:に比例しませんa%b。最悪の場合には、時にあるab連続したフィボナッチ数です。
Jerry Coffin

3
@JerryCoffin注:最悪のケースが確かにフィボナッチ数であることをより正式な方法で証明したい場合は、終了前のn番目のステップが少なくとも数学的誘導を伴うn番目のフィボナッチ数のgcd倍であることを証明することを検討してください。
マイゴッド2017年

回答:


73

ユークリッドのアルゴリズムの時間の複雑さを分析するための1つのトリックは、2つの反復で何が起こるかを追跡することです。

a', b' := a % b, b % (a % b)

これで、aとbは1つだけではなく、両方とも減少し、分析が容易になります。あなたはそれをケースに分けることができます:

  • タイニーA: 2a <= b
  • タイニーB: 2b <= a
  • スモールA:2a > bしかしa < b
  • 小文字B:2b > aしかしb < a
  • 等しい: a == b

次に、すべてのケースで合計a+bが少なくとも4分の1 減少することを示します。

  • タイニーA:b % (a % b) < a2a <= bなのでb、少なくとも半分は減少しているため、少なくとも半分はa+b減少しています25%
  • タイニーB:a % b < b2b <= a、のでa、少なくとも半分a+b減少し、少なくとも減少25%
  • 小A:bb-a、未満、少なくともb/2減少a+b25%ます。
  • 小さいB:aa-b、未満、少なくともa/2減少a+b25%ます。
  • 等しい:にa+b低下します0。これは明らかa+bに少なくとも減少してい25%ます。

したがって、ケース分析では、すべての2ステップがa+b少なくとも減少し25%ます。これa+bが強制的に下回る前に、これが発生する可能性のある最大回数があります1S0に到達するまでのステップ()の総数は、満たす必要があり(4/3)^S <= A+Bます。今ちょうどそれを働かせます:

(4/3)^S <= A+B
S <= lg[4/3](A+B)
S is O(lg[4/3](A+B))
S is O(lg(A+B))
S is O(lg(A*B)) //because A*B asymptotically greater than A+B
S is O(lg(A)+lg(B))
//Input size N is lg(A) + lg(B)
S is O(N)

したがって、反復回数は入力桁数に比例します。CPUレジスターに収まる数値の場合、一定の時間をとるように反復をモデル化し、gcdの合計実行時間は線形であるように見せかけるのが妥当です。

もちろん、大きな整数を扱う場合は、各反復内のモジュラス演算には一定のコストがないという事実を考慮する必要があります。大まかに言えば、漸近実行時間の合計は、多対数係数のn ^ 2倍になります。のようなもの n^2 lg(n) 2^O(log* n)。代わりにバイナリgcdを使用することで、多重対数係数を回避できます。


「b%(a%b)<a」となる理由を説明していただけますか?
Michael Heidelberg

3
@MichaelHeidelbergは、x % y以上のことはできませんx未満でなければなりませんya % bせいぜいそうであり、せいぜい下にあるため、全体としてはより少ないものをa強制b % (a%b)します。aa
Craig Gidney、2017

@ Cheersandhth.-Alf優先用語のわずかな違いは「深刻な誤り」であると考えていますか?もちろん、CS用語を使用しました。コンピュータサイエンスの問題です。いずれにせよ、「桁数」という答えを明確にしました。
Craig Gidney 2017

@CraigGidney:修正してくれてありがとう。今、私は純粋な学者によって書かれた多くのウィキペディアの記事からコミュニケーションの問題を認識しています。これを考慮してください:私がコメントで行ったようにO(log(min(a、b))と書くだけでなく、桁数について話す主な理由は、非数学的な人々のために物事を理解しやすくするためです。心配は「ログ」などと書くだけです。これが桁数の目的であり、挑戦者を助けます。この概念に「サイズ」という名前付け、他の場所で定義を行い、「ログ」については話さないでください。終わりに、あなたは助けの代わりに不明瞭になります
乾杯とhth。-Alf

最後の段落は正しくありません。関連する伸縮シリーズを合計すると、教科書2次時分割アルゴリズムを使用した場合でも、時間の複雑さはO(n ^ 2)であることがわかります。
EmilJeřábek

27

アルゴリズムを分析する適切な方法は、その最悪のシナリオを決定することです。ユークリッドGCDの最悪のケースは、フィボナッチペアが関係しているときに発生します。 void EGCD(fib[i], fib[i - 1])、ここでi> 0。

たとえば、被除数が55で除数が34の場合を選択してみましょう(まだフィボナッチ数を扱っていることを思い出してください)。

ここに画像の説明を入力してください

お気づきかもしれませんが、この操作には8回の反復(または再帰呼び出し)が必要です。

より大きなフィボナッチ数、つまり121393と75025を試してみましょう。ここでも、24回の反復(または再帰呼び出し)がかかったことがわかります。

ここに画像の説明を入力してください

また、各反復でフィボナッチ数が得られることにも注意してください。それが、私たちが非常に多くの業務を行っている理由です。実際、フィボナッチ数だけでは同様の結果を得ることができません。

したがって、時間の複雑さは、今回は小さいOh(上限)で表されます。下限は直感的にOmega(1)です。たとえば、500を2で割った場合です。

再帰関係を解決しましょう:

ここに画像の説明を入力してください

その場合、ユークリッドGCDは最大で log(xy)演算を実行できると言えます。


2
ベースは入力に依存するため、この分析は間違っていると思います。
うまくいけば

依存ベースが問題であることを証明できますか?
Mohamed Ennahdi El Idrissi 2017年

1
ベースは明らかに黄金比です。どうして?なぜなら、nod(13,8)とnod(8,5)を計算するには、1つの追加のステップが必要だからです。固定xの場合、y <xの場合、最悪の場合のパフォーマンスはx = fib(n + 1)、y = fib(n)です。ここで、yはxに依存するため、xのみを見ることができます。
ステパン2017

17

ウィキペディアの記事でこれをよく見てください

値のペアの複雑さのすてきなプロットさえあります。

そうではありませんO(a%b)

小さい数字の桁数の5倍を超えるステップは決してとらないことがわかっています(記事を参照)。したがって、最大ステップ数は、桁数が増えるにつれて大きくなります(ln b)。各ステップのコストも桁数が増えるにつれて増加するため、複雑さはO(ln^2 b)bが小さい数によって制限されます。これは上限であり、実際の時間は通常はそれより短くなります。


何をn表していますか?
IVlad

@IVlad:桁数。私は答えを明確にしました、ありがとう。
JoshD 2010

(大きな整数)除算(減算ではない)を使用するOPのアルゴリズムでは、実際にはO(n ^ 2 log ^ 2n)に似ています。
アレクサンドルC.

@Alexandre C .:覚えておいてくださいn = ln b。big intのモジュラスの通常の複雑さは何ですか?O(log n log ^ 2 log n)
JoshD

@JoshD:それはそのようなものです、私はlog n項を逃したと思います、この場合の最終的な複雑さ(除算のアルゴリズムの場合)はO(n ^ 2 log ^ 2 n log n)です。
アレクサンドルC.

13

こちらをご覧ください

特にこの部分:

ラメは、n未満の2つの数の最大公約数に到達するために必要なステップ数は

代替テキスト

したがってO(log min(a, b))、適切な上限です。


3
これはステップの数についても当てはまりますが、桁数(ln n)に比例する各ステップ自体の複雑さは考慮されていません。
JoshD 2010

9

ユークリッドのアルゴリズムの実行時の複雑さを直感的に理解できます。正式な証明は、Introduction to AlgorithmsやTAOCP Vol 2などのさまざまなテキストでカバーされています。

最初に、2つのフィボナッチ数F(k + 1)とF(k)のgcdを取得しようとした場合について考えます。ユークリッドのアルゴリズムがF(k)とF(k-1)に反復することをすぐに観察できます。つまり、反復ごとに、フィボナッチシリーズの数値を1つ下に移動します。フィボナッチ数はO(Phi ^ k)であり、Phiは黄金比です。GCDの実行時間はO(log n)であり、n = max(a、b)であり、logはPhiの底を持っています。次に、これが最悪のケースであることを証明できます。フィボナッチ数が一貫してペアを生成し、残りが各反復​​で十分な大きさのままであり、シリーズの開始に到達するまでゼロにならないことを確認することで証明できます。

O(log n)を作成することができます。ここで、n = max(a、b)はより厳密にバインドされています。b> = aと仮定して、O(log b)でboundを書き込みます。まず、GCD(ka、kb)= GCD(a、b)であることを確認します。kの最大値はgcd(a、c)であるため、ランタイムでbをb / gcd(a、b)に置き換えると、O(log b / gcd(a、b))の境界がより厳密になります。


フィボナッチ数列がユークリッドアルゴの最悪のケースを生成していることを正式に証明できますか?
Akash

4

ユークリッドアルゴリズムの最悪のケースは、各ステップで余りが最大になる場合です。フィボナッチ数列の2つの連続した項について。

nおよびmがaおよびbの桁数である場合、n> = mと想定すると、アルゴリズムはO(m)除算を使用します。

複雑さは常に入力のサイズ(この場合は桁数)で与えられることに注意してください。


4

最悪のケースは、nとmの両方が連続するフィボナッチ数である場合に発生します。

gcd(Fn、Fn−1)= gcd(Fn−1、Fn−2)=⋯= gcd(F1、F0)= 1で、n番目のフィボナッチ数は1.618 ^ nで、1.618は黄金比です。

したがって、gcd(n、m)を見つけるには、再帰呼び出しの数はΘ(logn)になります。


3

これは本の分析です Cにおけるデータ構造とアルゴリズム解析により、マーク・アレンワイス(第二版、2.4.4):

ユークリッドのアルゴリズムは、0に達するまで残りを計算し続けることで機能します。最後のゼロ以外の剰余が答えです。

これがコードです:

unsigned int Gcd(unsigned int M, unsigned int N)
{

    unsigned int Rem;
    while (N > 0) {
        Rem = M % N;
        M = N;
        N = Rem;
    }
    Return M;
}

がここにあります これから使用定理です。

M> Nの場合その後、 Mのmod N <M / 2。

証明:

2つのケースがあります。N <= M / 2の場合、剰余はNより小さいため、この場合の定理は真です。もう1つのケースは、N> M / 2です。しかし、Nは、M-N <M / 2で一度Mに入り、定理を証明します。

したがって、以下の推論を行うことができます。

Variables    M      N      Rem

initial      M      N      M%N

1 iteration  N     M%N    N%(M%N)

2 iterations M%N  N%(M%N) (M%N)%(N%(M%N)) < (M%N)/2

したがって、2回の反復後、残りは最大で元の値の半分になります。これは、反復回数が最大で2logN = O(logN)ます。

M> = Nと仮定して、アルゴリズムはGcd(M、N)を計算することに注意してください(N> Mの場合、ループの最初の反復でスワップされます)。


2

ガブリエルラメの定理は、log(1 / sqrt(5)*(a + 1/2))-2によってステップ数を制限します。ここで、対数の底は(1 + sqrt(5))/ 2です。これは、アルゴリズムの最悪の場合のシーンリオ用であり、入力が連続するフィバノッチ数である場合に発生します。

少しリベラルな境界は次のとおりです。ログa、ログのベースは(sqrt(2))であり、Koblitzによって暗示されます。

暗号化の目的で、ビットサイズがおよそk = logaによって与えられることを考慮して、通常、アルゴリズムのビット単位の複雑さを考慮します。

ユークリッドアルゴリズムのビット単位の複雑さの詳細な分析を次に示します。

ほとんどの参考文献では、ユークリッドアルゴリズムのビット単位の複雑さはO(loga)^ 3によって与えられますが、O(loga)^ 2というより厳しい境界が存在します。

検討してください。r0 = a、r1 = b、r0 = q1.r1 + r2。。。、ri-1 = qi.ri + ri + 1 、. 。。、rm-2 = qm-1.rm-1 + rm rm-1 = qm.rm

a = r0> = b = r1> r2> r3 ...> rm-1> rm> 0 ..........(1)に注意してください。

そしてrmはaとbの最大公約数です。

コブリッツの本のクレーム(理論と暗号のコース)は、ri + 1 <(ri-1)/ 2 .................( 2)

Koblitzでは、kビットの正の整数をlビットの正の整数で除算するために必要なビット演算の数(k> = lと仮定)は、次のように与えられます:(k-l + 1).l ...... .............(3)

(1)と(2)によって除算の数はO(loga)になるため、(3)によって合計の複雑度はO(loga)^ 3になります。

これは、Koblitzの発言によりO(loga)^ 2に削減される可能性があります。

ki = logri +1を考慮

(1)と(2)で、ki + 1 <= ki for i = 0,1、...、m-2、m-1とki + 2 <=(ki)-1 for i = 0 、1、...、m-2

(3)により、m個の分割の合計コストは次のように制限されます:SUM [(ki-1)-((ki)-1))] * ki for i = 0,1,2、..、m

これを再配置:SUM [(ki-1)-((ki)-1))] * ki <= 4 * k0 ^ 2

したがって、ユークリッドのアルゴリズムのビットごとの複雑さはO(loga)^ 2です。


1

ただし、反復アルゴリズムの場合、次のようになります。

int iterativeEGCD(long long n, long long m) {
    long long a;
    int numberOfIterations = 0;
    while ( n != 0 ) {
         a = m;
         m = n;
         n = a % n;
        numberOfIterations ++;
    }
    printf("\nIterative GCD iterated %d times.", numberOfIterations);
    return m;
}

フィボナッチペアでは、差がないiterativeEGCD()iterativeEGCDForWorstCase()、次のような後者のルックス:

int iterativeEGCDForWorstCase(long long n, long long m) {
    long long a;
    int numberOfIterations = 0;
    while ( n != 0 ) {
         a = m;
         m = n;
         n = a - n;
        numberOfIterations ++;
    }
    printf("\nIterative GCD iterated %d times.", numberOfIterations);
    return m;
}

はい、フィボナッチペアn = a % nn = a - n、それはまったく同じです。

また、同じ質問に対する以前の回答では、主な減少要因が存在することもわかっていますfactor = m / (n % m)

したがって、ユークリッドGCDの反復バージョンを定義された形式で形成するには、次のように「シミュレータ」として表すことができます。

void iterativeGCDSimulator(long long x, long long y) {
    long long i;
    double factor = x / (double)(x % y);
    int numberOfIterations = 0;
    for ( i = x * y ; i >= 1 ; i = i / factor) {
        numberOfIterations ++;
    }
    printf("\nIterative GCD Simulator iterated %d times.", numberOfIterations);
}

Jauhar Ali博士の研究(最後のスライド)に基づくと、上記のループは対数的です。

ここに画像の説明を入力してください

はい。シミュレーターは最大で反復回数指示するため、小さなああ。ユークリッドGCDでプローブした場合、フィボナッチ以外のペアの反復回数はフィボナッチよりも少なくなります。


この調査はC言語を使用して行われたため、精度の問題により誤った値や不正確な値が生じる可能性があります。これが、longという名前が使用された理由です。これは、factorという名前の浮動小数点変数に適合させるためです。使用されているコンパイラはMinGW 2.95です。
Mohamed Ennahdi El Idrissi 2014年

1

すべてのステップで、2つのケースがあります。

b> = a / 2、a、b = b、a%bの場合、bは以前の値の最大で半分になります

b <a / 2、次にa、b = b、a%bは、aが以前の値の最大半分になるため、bはa / 2より小さいため

したがって、すべてのステップで、アルゴリズムは少なくとも1つの数を少なくとも半分に減らします。

せいぜいO(log a)+ O(log b)ステップでは、これは単純なケースに削減されます。これは、O(log n)アルゴリズムを生成します。ここで、nはaとbの上限です。

ここで見つけまし

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.