GCDにとって最も効率的なものは何ですか?


26

ユークリッドのアルゴリズムは、正の整数のリストのGCD(最大公約数)を取得するための最適なアルゴリズムであることを知っています。しかし実際には、このアルゴリズムをさまざまな方法でコーディングできます。(私の場合、Javaを使用することにしましたが、C / C ++が別のオプションである可能性があります)。

プログラムで可能な限り最も効率的なコードを使用する必要があります。

再帰モードでは、次のように記述できます。

static long gcd (long a, long b){
    a = Math.abs(a); b = Math.abs(b);
    return (b==0) ? a : gcd(b, a%b);
  }

反復モードでは、次のようになります。

static long gcd (long a, long b) {
  long r, i;
  while(b!=0){
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}

GCD用のバイナリアルゴリズムもあります。これは、次のように簡単にコーディングできます。

int gcd (int a, int b)
{
    while(b) b ^= a ^= b ^= a %= b;
    return a;
}

3
これはあまりにも主観的で、おそらくStackOverflowにより適していると思います。「実際に、効率的な大部分は、」等下地architechture、メモリ階層、サイズ及び入力の形態として、多くの(さえ予測できない)要因に依存
たJuho

5
これは、再帰的および反復的な方法で表現された同じアルゴリズムです。ユークリッドアルゴリズムは非常に高速に収束するため、それらの違いは無視できると思います。好みに合ったものを選択してください。
パッド

6
これら2つをプロファイリングしてみてください。再帰バージョンは末尾呼び出しであるため、コンパイラが実際にほぼ同じコードを出力する可能性は低くありません。
ルイス

1
これは間違っています。b!= 0の間に、aを返します。そうしないと、ゼロ除算でバグが発生します。また、本当に大きなgcdがある場合は、再帰を使用しないでください。...スタックと関数の状態の山を取得します...なぜ単に反復しないのですか?
クリスストリングフェロー

4
漸近的に高速なGCDアルゴリズムがあることに注意してください。例:en.wikipedia.org/wiki/Binary_GCD_algorithm
ニールヤング

回答:


21

2つのアルゴリズムは同等です(少なくとも正の整数の場合、命令型バージョンで負の整数で何が起こるかは、Javaのセマンティクスに依存します%)。再帰的なバージョンでは、聞かせてI及びbはiは引数であるI番目の再帰呼び出し: I + 1 = B I B iが+ 1 = I M O D B Iab

a+1=bb+1=amodb

命令型ではおよびb ' iを変数の値とし、ループのi番目の反復の開始時とします。 a i + 1 = b i b i + 1 = a i m o d b iabab

a+1=bb+1=amodb

類似点に注意してください?命令型と再帰型はまったく同じ値を計算しています。さらに、それらは両方とも(それぞれa ' i = 0)のときに同時に終了するため、同じ回数の反復を実行します。アルゴリズム的に言えば、2つの間に違いはありません。違いは実装の問題であり、コンパイラ、実行するハードウェア、およびオペレーティングシステムと他のプログラムが同時に実行しているものに大きく依存します。a=0a=0

再帰バージョンは末尾再帰呼び出しのみを行います。命令型言語のほとんどのコンパイラはこれらを最適化しないため、生成するコードは各反復でスタックフレームを構築するために少しの時間とメモリを浪費する可能性があります。末尾呼び出しを最適化するコンパイラー(関数型言語のコンパイラーはほとんどの場合実行します)では、生成されたマシンコードは両方で同じになる可能性があります(これらの呼び出しを調和させると仮定abs


8

数値が小さい場合、バイナリGCDアルゴリズムで十分です。

十分に管理され、実際にテストされたライブラリであるGMPは、特別なしきい値、Lehmerのアルゴリズムの一般化を通過した後、特別なハーフGCDアルゴリズムに切り替わります。Lehmer'sは、行列乗算を使用して、標準のユークリッドアルゴリズムを改善します。ドキュメントによると、HGCDとGCDの両方の漸近的実行時間はO(M(N)*log(N))であり、ここでM(N)2つのNリム数を乗算する時間です。

それらのアルゴリズムの完全な詳細はここで見つけることができます


リンクは完全な詳細を実際に提供せず、「手足」が何であるかさえ定義していません...
einpoklum-モニカを


2

私が知っているように、Javaは一般に末尾再帰の最適化をサポートしていませんが、Java実装をテストできます。サポートされていない場合は、単純なfor-loopがより高速になり、そうでない場合は再帰が同様に高速になります。一方、これらはビット最適化であり、より簡単で読みやすいと思うコードを選択してください。

また、最速のGCDアルゴリズムはユークリッドのアルゴリズムではなく、レーマーのアルゴリズムは少し高速であることに注意してください。


あなたは意味するかのようにこれまで私が知っているように?言語の仕様ではこの最適化が義務付けられていないということですか(それは驚くべきことですが)、またはほとんどの実装では実装されていません。
PJTraill

1

まず、タイトなループを置き換えるために再帰を使用しないでください。遅いです。最適化のためにコンパイラに依存しないでください。次に、コードでは、再帰呼び出しごとにMath.abs()を呼び出しますが、これは役に立ちません。

ループでは、一時変数を簡単に回避し、aとbを常に交換できます。

int gcd(int a, int b){
    if( a<0 ) a = -a;
    if( b<0 ) b = -b;
    while( b!=0 ){
        a %= b;
        if( a==0 ) return b;
        b %= a;
    }
    return a;
}

a ^ = b ^ = a ^ = bを使用してスワップすると、ソースが短くなりますが、実行するのに多くの命令が必要になります。一時変数を使用した退屈なスワップよりも遅くなります。


3
「再帰性を避けます。一般的なアドバイスとして提示される、これは偽です。コンパイラに依存します。通常、再帰を最適化しないコンパイラでも、遅くはなく、スタックを消費します。
ジル「SO-悪であるのをやめる」

3
しかし、このような短いコードの場合、違いは重要です。スタック消費とは、メモリへの書き込みとメモリからの読み取りを意味します。それは遅いです。上記のコードは2つのレジスタで実行されます。再帰性は、条件付きジャンプよりも長い呼び出しを行うことも意味します。再帰呼び出しは、分岐の予測がはるかに難しく、インライン化が困難です。
フロリアンF

-2

以下のために小さな数字、%は、おそらく単純な再帰的な、非常に高価な操作です

GCD[a,b] := Which[ 
   a==b , Return[a],
   b > a, Return[ GCD[a, b-a]],
   a > b, Return[ GCD[b, a-b]]
];

速いですか?(申し訳ありませんが、C ++ではなくMathematicaコード)


正しく見えません。b == 1の場合、1を返します。GCD[2,1000000000]は遅くなります。
フロリアンF 14

ああ、はい、私は間違いを犯しました。修正(と思う)、および明確化。
アレキサンダーソンごと14

通常、GCD [a、0]もaを返す必要があります。あなたのものは永遠にループします。
フロリアンF 14

あなたの答えにはコードしか含まれていないので、私は投票しています。このサイトのアイデアに集中したいです。たとえば、なぜコストが高いのですか?私の意見では、コードの一部に対する推測は、このサイトにとって本当に良い答えではありません。
ジュホ

1
モジュロは減算より遅いという考えは、民間伝承と考えることができます。小さい整数(減算は通常1サイクルかかり、モジュロはめったにありません)と大きい整数(減算は線形です。モジュロにとって最適な複雑さはわかりませんが、それよりも明らかに悪いです)の両方に当てはまります。もちろん、必要な反復の数も考慮する必要があります。
ジル 'SO-悪であるのをやめる'

-2

ユークリッドアルゴリズムは、GCDの計算に最も効率的です。

静的long gcd(long a、long b)
{
if(b == 0)
を返します;
他に
return gcd(、a%b);
}

例:-

A = 16、B = 10とする。
GCD(16、10)= GCD(10、16%10)= GCD(10、6)
GCD(10、6)= GCD(6、10%6)= GCD(6、4)
GCD(6、4)= GCD(4、6%4)= GCD(4、2)
GCD(4、2)= GCD(2、4%2)= GCD(2、0)


B = 0なので、GCD(2、0)は2を返します。 

4
これは質問に答えません。質問者はユークリッドの2つのバージョンを提示し、どちらが速いかを尋ねます。気付いていないようで、単に再帰バージョンが唯一のユークリッドのアルゴリズムであると宣言し、他のすべてよりも高速であるという証拠はまったくありません。
デビッドリチャービー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.