Qual è la più efficiente per GCD?


26

So che l'algoritmo di Euclide è l'algoritmo migliore per ottenere il GCD (grande divisore comune) di un elenco di numeri interi positivi. Ma in pratica puoi codificare questo algoritmo in vari modi. (Nel mio caso, ho deciso di utilizzare Java, ma C / C ++ potrebbe essere un'altra opzione).

Devo usare il codice più efficiente possibile nel mio programma.

In modalità ricorsiva, puoi scrivere:

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

E in modalità iterativa, assomiglia a questo:

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

Esiste anche l'algoritmo binario per GCD, che può essere codificato semplicemente in questo modo:

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

3
Penso che questo sia troppo soggettivo e forse anche più adatto a StackOverflow. "Il più efficiente in pratica" dipende da molti fattori (anche imprevedibili), come l'architettura sottostante, la gerarchia della memoria, le dimensioni e la forma dell'input, ecc.
Juho

5
Questo è lo stesso algoritmo espresso in modi ricorsivi e iterativi. Penso che la loro differenza sia trascurabile poiché l'algoritmo Euclid converge abbastanza velocemente. Scegli quello che si adatta alle tue preferenze.
pad

6
Potresti provare a profilare questi due. Poiché la versione ricorsiva è un richiamo di coda, non è improbabile che il compilatore emetta quasi lo stesso codice.
Louis,

1
questo è sbagliato. dovrebbe essere while b! = 0, quindi restituire a. Altrimenti si esclude in divisione per zero. inoltre non usare la ricorsione se hai gcd davvero grandi .... hai un mucchio di stati di stack e funzioni ... perché non andare semplicemente iterativo?
Cris Stringfellow,

4
Si noti che esistono algoritmi GCD asintoticamente più veloci. Ad esempio en.wikipedia.org/wiki/Binary_GCD_algorithm
Neal Young

Risposte:


21

I tuoi due algoritmi sono equivalenti (almeno per gli interi positivi, ciò che accade con gli interi negativi nella versione imperativa dipende dalla semantica di Java per la %quale non lo so a memoria). Nella versione ricorsiva, permetta e b i essere l'argomento della i esima chiamata ricorsiva: un i + 1 = b i b i + 1 = un i m o d b iaibii

ai+1=bibi+1=aimodbi

aibiabi

ai+1=bibi+1=aimodbi

ai=0ai=0

La versione ricorsiva effettua solo chiamate ricorsive di coda . La maggior parte dei compilatori per i linguaggi imperativi non li ottimizza, quindi è probabile che il codice che generano sprecherà un po 'di tempo e memoria costruendo un frame stack ad ogni iterazione. Con un compilatore che ottimizza le chiamate di coda (compilatori per linguaggi funzionali quasi sempre lo fanno), il codice macchina generato potrebbe essere lo stesso per entrambi (supponendo che si armonizzino quelle chiamate abs).


8

Per numeri piccoli, l'algoritmo binario GCD è sufficiente.

GMP, una libreria ben mantenuta e testata nel mondo reale, passerà a uno speciale algoritmo a metà GCD dopo aver superato una soglia speciale, una generalizzazione dell'algoritmo di Lehmer. Lehmer's utilizza la moltiplicazione di matrici per migliorare gli algoritmi euclidi standard. Secondo i documenti, il tempo di esecuzione asintotico di entrambi HGCD e GCD è O(M(N)*log(N)), dove M(N)è il tempo per moltiplicare due numeri N-arto.

I dettagli completi sul loro algoritmo sono disponibili qui .


Il link in realtà non fornisce dettagli completi e non definisce nemmeno cosa sia un "arto" ...
einpoklum - ripristina Monica


2

Come so, Java non supporta l'ottimizzazione della ricorsione della coda in generale, ma è possibile testarne l'implementazione; se non lo supporta, un semplice for-loop dovrebbe essere più veloce, altrimenti la ricorsione dovrebbe essere altrettanto veloce. D'altra parte, si tratta di ottimizzazioni di bit, scegli il codice che ritieni sia più facile e più leggibile.

Dovrei anche notare che l'algoritmo GCD più veloce non è l'algoritmo di Euclide, l'algoritmo di Lehmer è un po 'più veloce.


Vuoi dire come lontano come io so ? Vuoi dire che le specifiche del linguaggio non impongono questa ottimizzazione (sarebbe sorprendente se lo facesse) o che la maggior parte delle implementazioni non la implementano?
PJTraill,

1

Innanzitutto, non utilizzare la ricorsività per sostituire un circuito chiuso. È lento. Non fare affidamento sul compilatore per ottimizzarlo. Secondo, nel tuo codice, chiami Math.abs () all'interno di ogni chiamata ricorsiva, il che è inutile.

Nel tuo ciclo, puoi facilmente evitare variabili temporanee e scambiare aeb continuamente.

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;
}

Lo scambio usando a ^ = b ^ = a ^ = b rende la fonte più breve ma richiede molte istruzioni per l'esecuzione. Sarà più lento dello scambio noioso con una variabile temporanea.


3
“Evita la ricorsività. È lento ”- presentato come consiglio generale, questo è falso. Dipende dal compilatore. Di solito, anche con compilatori che non ottimizzano la ricorsione, non è lento, ma consuma solo stack.
Gilles 'SO- smetti di essere malvagio' il

3
Ma per codici brevi come questo, la differenza è significativa. Consumo di stack significa scrivere e leggere dalla memoria. È lento. Il codice sopra funziona su 2 registri. La ricorsività significa anche fare chiamate, che è più lunga di un salto condizionale. Una chiamata ricorsiva è molto più difficile per la previsione delle filiali e più difficile da incorporare.
Florian F,

-2

Per piccoli numeri ,% è un'operazione piuttosto costosa, forse la più semplice ricorsiva

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

è più veloce? (Siamo spiacenti, codice Mathematica e non C ++)


Non sembra giusto. Per b == 1, dovrebbe restituire 1. E GCD [2.1000000000] sarà lento.
Florian F,

Ah, sì, ho fatto un errore. Risolto (penso) e chiarito.
Per Alexandersson,

Normalmente, GCD [a, 0] dovrebbe anche restituire a. I tuoi loop per sempre.
Florian F,

Sto effettuando il downgrade poiché la tua risposta contiene solo codice. Ci piace concentrarci sulle idee di questo sito. Ad esempio, perché% è un'operazione costosa? La speculazione su un pezzo di codice non è davvero una buona risposta per questo sito, secondo me.
Juho,

1
Penso che l'idea che il modulo sia più lento della sottrazione possa essere considerata folklore. È valido sia per numeri interi piccoli (la sottrazione di solito richiede un ciclo, modulo raramente lo fa) sia per numeri interi grandi (la sottrazione è lineare, non sono sicuro di quale sia la migliore complessità per modulo, ma è decisamente peggiore). Ovviamente devi anche considerare il numero di iterazioni necessarie.
Gilles 'SO- smetti di essere malvagio' il

-2

Euclid Algorithm è il più efficace per il calcolo del GCD:

Gcd lungo statico (lungo a, lungo b)
{
if (b == 0)
restituire a;
altro
restituisce gcd (, a% b);
}

esempio:-

Sia 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)


Poiché B = 0, quindi GCD (2, 0) restituirà 2. 

4
Questo non risponde alla domanda. Il richiedente presenta due versioni di Euclide e chiede quale sia più veloce. Sembra che tu non l'abbia notato e dichiari semplicemente la versione ricorsiva come l'unico algoritmo di Euclide e asserisci senza alcuna prova che è più veloce di ogni altra cosa.
David Richerby,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.