GCD için en verimli olan nedir?


26

Euclid'in algoritmasının, GCD'yi (büyük ortak bölen) bir pozitif tamsayı listesinden almak için en iyi algoritma olduğunu biliyorum. Ancak pratikte bu algoritmayı çeşitli şekillerde kodlayabilirsiniz. (Benim durumumda Java kullanmaya karar verdim, fakat C / C ++ başka bir seçenek olabilir).

Programımda mümkün olan en verimli kodu kullanmam gerekiyor.

Özyinelemeli modda, şunları yazabilirsiniz:

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

Yinelemeli modda şöyle görünür:

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

GCD için basitçe şöyle kodlanabilecek İkili algoritma da vardır:

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

3
Bunun çok öznel olduğunu ve belki de StackOverflow için daha uygun olduğunu düşünüyorum. "Pratikte verimli çoğu" vb yatan mimarisi, hafıza hiyerarşisi, boyut ve giriş biçimi olarak, birçok (hatta öngörülemeyen) faktörlere bağlıdır
Juho

5
Bu özyinelemeli ve yinelemeli şekillerde ifade aynı algoritma. Öklid algoritması oldukça hızlı bir şekilde birleştiği için farklarının önemsiz olduğunu düşünüyorum. Tercihinize uygun olanı seçin.
ped

6
Bu ikisini profillemeyi denemek isteyebilirsiniz. Özyinelemeli sürüm bir kuyruk çağrısı olduğundan, derleyicinin neredeyse aynı kodu vermesi pek olası değildir.
Louis,

1
Bu yanlış. b! = 0 iken olmalı ve sonra a döndür. Aksi taktirde, sıfıra bölünerek patlar. Ayrıca eğer gerçekten büyük gcds varsa, özyinelemeyi kullanmayın .... bir yığın ve işlev durumları yığını alırsınız ... neden sadece yinelemeli değil?
Cris Stringfellow

4
Asimptotik olarak daha hızlı GCD algoritmaları olduğunu unutmayın. Eg en.wikipedia.org/wiki/Binary_GCD_algorithm
Neal Young

Yanıtlar:


21

İki algoritmanız eşdeğerdir (en azından pozitif tamsayılar için, zorunlu sürümde negatif tamsayılarla olan şey, Java'nın %yürekten bilmediğim anlambilimine bağlıdır ). Özyinelemeli sürümünde, let ve ait argüman inci özyinelemeli çağrı: b ı ı bir i + 1 = b i b ı + 1 = bir ı m O d b iaibii

ai+1=bibi+1=aimodbi

Zorunlu sürümünde, let ve değişkenlerin değerleri olması ve başında döngünün inci yineleme. b ' i i , bir ' i + 1 = b ' ı b ' i + 1 = bir ' ı m O d b ' ıaibiabi

ai+1=bibi+1=aimodbi

Bir benzerlik farkettiniz mi? Zorunlu versiyonunuz ve özyinelemeli versiyonunuz tamamen aynı değerleri hesaplıyor. Bundan başka, aynı zamanda her iki ucu, zaman (sırasıyla. ), iterasyonlar aynı sayıda gerçekleştirebilirler. Yani algoritmik olarak konuşursak, ikisi arasında bir fark yoktur. Herhangi bir fark, derleyiciye, çalıştığı donanıma ve büyük olasılıkla işletim sistemine ve diğer programların aynı anda çalıştığı duruma bağlı olarak bir uygulama meselesi olacaktır.a i = 0ai=0ai=0

Özyinelemeli sürüm sadece kuyruk özyinelemeli aramalar yapar . Zorunlu diller için çoğu derleyici bunları optimize etmemektedir ve bu nedenle ürettikleri kodun biraz zaman kaybetmesi ve her yinelemede bir yığın çerçeve oluşturmak için bellek harcaması muhtemeldir. Kuyruk çağrılarını optimize eden bir derleyici ile (neredeyse her zaman işlevsel diller için derleyiciler yapar), üretilen makine kodu her ikisi için de aynı olabilir (bu çağrıları uyumlu hale getirdiğinizi varsayarak abs).


8

Küçük sayılar için ikili GCD algoritması yeterlidir.

İyi korunmuş ve gerçek dünyada test edilmiş bir kütüphane olan GMP, Lehmer'in Algoritmasının genelleştirmesi olan özel bir eşiği geçtikten sonra özel bir yarı GCD algoritmasına geçecektir. Lehmer, standart Euclidian algoritmalarını geliştirmek için matris çarpımını kullanıyor. Docs göre hGCD ve GCD'nın hem asimptotik çalışan zamanıdır O(M(N)*log(N)), nerede M(N)iki N-bacak sayının çarpımı zamanıdır.

Algoritmalarıyla ilgili tüm detayları burada bulabilirsiniz .


Bağlantı gerçekten tam bir ayrıntı sağlamıyor ve hatta bir "uzuv" nın ne olduğunu bile tanımlamıyor ...
einpoklum - Monica’yı


2

Java'nın genel olarak kuyruk özyineleme optimizasyonunu desteklemediğini bildiğim için, ancak Java uygulamanızı bunun için test edebilirsiniz; eğer desteklemiyorsa, basit bir fordöngü daha hızlı olmalı, aksi takdirde özyineleme aynı derecede hızlı olmalıdır. Öte yandan, bunlar bit optimizasyonları, daha kolay ve daha okunaklı olduğunu düşündüğünüz kodu seçin.

Ayrıca en hızlı GCD algoritmasının Euclid'in algoritması olmadığını, Lehmer'in algoritmasının biraz daha hızlı olduğunu da belirtmeliyim .


Şunu musunuz olarak uzak bildiğim kadar ? Dil spesifikasyonunun bu optimizasyonu zorunlu kılmadığını mı düşünüyorsunuz (eğer yapması şaşırtıcı olurdu) ya da çoğu uygulamanın uygulamaması?
PJTraill

1

İlk olarak, sıkı bir döngüyü değiştirmek için özyinelemeyi kullanmayın. Bu yavaş. Optimize etmek için derleyiciye güvenmeyin. İkincisi, kodunuzda, her özyinelemeli çağrıların içinde Math.abs () yöntemini çağırırsınız, bu işe yaramaz.

Döngünüzde geçici değişkenleri kolayca önleyebilir ve a ve b'yi her zaman değiştirebilirsiniz.

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 kullanarak değiştirmek, kaynağı kısaltır ancak yürütülmesi için birçok talimat alır. Geçici değişkenli sıkıcı takastan daha yavaş olacaktır.


3
“Özyinelemeden kaçının. Yavaş ”- genel bir öneri olarak sunuldu, bu sahte. Derleyiciye bağlıdır. Genellikle, özyinelemeyi optimize etmeyen derleyicilerde bile yavaş olmaz, yalnızca yığın harcar.
Gilles 'SO- kötülük' dur

3
Ancak bu gibi kısa kodlar için fark önemlidir. Yığın tüketen, belleğe yazmak ve hafızadan okumak anlamına gelir. Bu yavaş. Yukarıdaki kod 2 kayıt üzerinde çalışır. Özyineleme, koşullu bir sıçramadan daha uzun olan arama yapmak anlamına da gelir. Özyinelemeli bir çağrı, dal tahmini için daha zordur ve satır içi için daha zordur.
Florian F

-2

İçin az sayıda ,% belki daha basit özyinelemeli oldukça pahalı bir işlemdir

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

daha hızlı mı? (Üzgünüz, Mathematica kodu ve C ++ değil)


Doğru görünmüyor. B == 1 için 1 döndürmelidir. GCD [2,1000000000] yavaş olacaktır.
Florian F

Ah, evet, bir hata yaptım. Sabit (bence) ve netleştirildi.
Alexandersson Per

Normalde, GCD [a, 0] da a döndürmelidir. Seninki sonsuza dek döngüler.
Florian F

Cevabınız sadece kod içerdiğinden aşağı oy kullanıyorum. Bu sitedeki fikirlere odaklanmayı seviyoruz. Örneğin,% neden pahalı bir işlemdir? Kanuna göre bir kod spekülasyonu bu site için iyi bir cevap değil bence.
Juho,

1
Modulo'nun çıkartmadan daha yavaş olduğu fikrinin folklor olabileceği kanaatindeyim. Hem küçük tamsayılar için (çıkarma genellikle bir döngü alır, nadiren modulo yapar) ve büyük tamsayılar için (çıkarma doğrusaldır, modulo için en iyi karmaşıklığın ne olduğundan emin değilim ama kesinlikle bundan daha kötü). Tabii ki gerekli yinelemelerin sayısını da göz önünde bulundurmanız gerekir.
Gilles 'SO- kötülük' dur

-2

Öklid Algoritması, GCD'yi hesaplamak için en etkilidir:

Statik uzun gcd (uzun a, uzun b)
{
(b == 0)
a dönüş;
Başka
gcd geri dönüşü (,% b);
}

örnek:-

A = 16, B = 10 olsun.
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 olduğundan GCD (2, 0) 2 döndürür. 

4
Bu soruya cevap vermiyor. Asker, Euclid'in iki versiyonunu sunar ve hangisinin daha hızlı olduğunu sorar. Bunu farketmiş görünmüyorsunuz ve özyinelemeli sürümü sadece Euclid'in algoritması olarak ilan ettiniz ve başka hiçbir şeyden daha hızlı olduğuna dair hiçbir kanıt olmadan iddiada bulunuyorsunuz.
David Richerby
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.