Complessità temporale dell'algoritmo di Euclide


97

Ho difficoltà a decidere quale sia la complessità temporale dell'algoritmo del massimo comune denominatore di Euclide. Questo algoritmo in pseudo-codice è:

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

Sembra dipendere da a e b . Il mio pensiero è che la complessità temporale sia O (a% b). È corretto? C'è un modo migliore per scriverlo?


14
Vedi Knuth TAOCP, Volume 2 - fornisce un'ampia copertura. Solo FWIW, un paio di bocconcini: non è proporzionale a a%b. Il caso peggiore è quando ae bsono numeri di Fibonacci consecutivi.
Jerry Coffin

3
@ JerryCoffin Nota: se vuoi dimostrare che il caso peggiore è davvero i numeri di Fibonacci in un modo più formale, considera di dimostrare che l'n-esimo passaggio prima della terminazione deve essere almeno grande quanto mcd volte l'n-esimo numero di Fibonacci con induzione matematica.
Mygod

Risposte:


73

Un trucco per analizzare la complessità temporale dell'algoritmo di Euclide è seguire ciò che accade in due iterazioni:

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

Ora aeb diminuiranno entrambi, invece di uno solo, il che facilita l'analisi. Puoi dividerlo in casi:

  • Tiny A: 2a <= b
  • Tiny B: 2b <= a
  • Piccolo A: 2a > bmaa < b
  • Piccola B: 2b > amab < a
  • Pari: a == b

Ora mostreremo che ogni singolo caso riduce il totale a+bdi almeno un quarto:

  • Tiny A: b % (a % b) < ae 2a <= b, quindi bè diminuito di almeno la metà, quindi è a+bdiminuito almeno25%
  • Tiny B: a % b < be 2b <= a, quindi aè diminuito di almeno la metà, quindi è a+bdiminuito almeno25%
  • A piccolo: bdiventerà b-a, che è minore di b/2, diminuendo a+balmeno 25%.
  • B piccola: adiventerà a-b, che è minore di a/2, diminuendo a+balmeno 25%.
  • Uguale: a+bscende a 0, che ovviamente sta diminuendo a+balmeno 25%.

Pertanto, in base all'analisi dei casi, ogni doppio passaggio diminuisce a+bdi almeno 25%. C'è un numero massimo di volte che ciò può accadere prima che a+bsia costretto a scendere sotto 1. Il numero totale di passaggi ( S) fino a quando non raggiungiamo 0 deve soddisfare (4/3)^S <= A+B. Ora lavoraci e basta:

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

Quindi il numero di iterazioni è lineare nel numero di cifre di input. Per i numeri che si adattano ai registri della cpu, è ragionevole modellare le iterazioni come se prendessero un tempo costante e fingere che il tempo di esecuzione totale del display LCD sia lineare.

Ovviamente, se hai a che fare con numeri interi grandi, devi tenere conto del fatto che le operazioni del modulo all'interno di ciascuna iterazione non hanno un costo costante. In parole povere, il tempo di esecuzione asintotico totale sarà n ^ 2 volte un fattore polilogaritmico. Qualcosa di simile n^2 lg(n) 2^O(log* n) . Il fattore polilogaritmico può essere evitato utilizzando invece un gcd binario .


Puoi spiegare perché "b% (a% b) <a" per favore?
Michael Heidelberg

3
@MichaelHeidelberg x % ynon può essere maggiore di xe deve essere minore di y. Così a % bè al massimo a, costringendo b % (a%b)a essere al di sotto di qualcosa che è al massimo ae quindi è complessivamente inferiore a a.
Craig Gidney

@ Cheersandhth.-Alf Considera "seriamente sbagliato" una leggera differenza nella terminologia preferita? Ovviamente ho usato la terminologia CS; è una domanda di informatica. Indipendentemente da ciò, ho chiarito la risposta per dire "numero di cifre".
Craig Gidney

@ CraigGidney: Grazie per aver risolto il problema. Ora riconosco il problema della comunicazione da molti articoli di Wikipedia scritti da accademici puri. Considera questo: il motivo principale per parlare di numero di cifre, invece di scrivere O (log (min (a, b)) come ho fatto nel mio commento, è rendere le cose più semplici da capire per le persone non matematiche. si tratta di scrivere semplicemente "registro", ecc. Quindi questo è lo scopo del numero di cifre, aiutare le persone sfidate. Quando si chiama questa nozione "dimensione" e si ha la definizione altrove, e non si parla di "registro" al
Alla

L'ultimo paragrafo non è corretto. Se si sommano le serie telescopiche pertinenti, si scopre che la complessità temporale è solo O (n ^ 2), anche se si utilizza l'algoritmo di divisione del tempo quadratico da manuale.
Emil Jeřábek

27

Il modo adatto per analizzare un algoritmo è determinare i suoi scenari peggiori. Il caso peggiore di Euclidean GCD si verifica quando sono coinvolte coppie di Fibonacci. void EGCD(fib[i], fib[i - 1]), dove i> 0.

Ad esempio, optiamo per il caso in cui il dividendo è 55 e il divisore è 34 (ricorda che abbiamo ancora a che fare con numeri di Fibonacci).

inserisci qui la descrizione dell'immagine

Come puoi notare, questa operazione è costata 8 iterazioni (o chiamate ricorsive).

Proviamo con numeri di Fibonacci più grandi, precisamente 121393 e 75025. Possiamo notare anche qui che ci sono volute 24 iterazioni (o chiamate ricorsive).

inserisci qui la descrizione dell'immagine

Puoi anche notare che ogni iterazione produce un numero di Fibonacci. Ecco perché abbiamo così tante operazioni. Non possiamo ottenere risultati simili solo con i numeri di Fibonacci.

Quindi, la complessità temporale sarà rappresentata da un piccolo Oh (limite superiore), questa volta. Il limite inferiore è intuitivamente Omega (1): caso di 500 diviso per 2, per esempio.

Risolviamo la relazione di ricorrenza:

inserisci qui la descrizione dell'immagine

Possiamo quindi dire che Euclidean GCD può eseguire al massimo operazioni log (xy) .


2
Penso che questa analisi sia sbagliata, perché la base dipende dall'input.
spera utile il

Puoi provare che una base dipendente rappresenta un problema?
Mohamed Ennahdi El Idrissi

1
La base è ovviamente la sezione aurea. Perché? Perché ci vuole esattamente un passo in più per calcolare nod (13,8) vs nod (8,5). Per una x fissa se y <x la prestazione nel caso peggiore è x = fib (n + 1), y = fib (n). Qui y dipende da x, quindi possiamo guardare solo x.
Stepan

17

C'è una grande occhiata a questo nell'articolo di wikipedia .

Ha anche una bella trama di complessità per le coppie di valori.

Non lo è O(a%b).

È noto (vedi articolo) che non farà mai più passi di cinque volte il numero di cifre nel numero più piccolo. Quindi il numero massimo di passaggi cresce con il numero di cifre (ln b). Anche il costo di ogni passaggio cresce con il numero di cifre, quindi la complessità è limitata da O(ln^2 b)dove b è il numero più piccolo. Questo è un limite massimo e il tempo effettivo di solito è inferiore.


Cosa nrappresenta?
IVlad

@IVlad: numero di cifre. Ho chiarito la risposta, grazie.
JoshD,

Per l'algoritmo di OP, utilizzando divisioni (big integer) (e non sottrazioni) è in effetti qualcosa di più simile a O (n ^ 2 log ^ 2n).
Alexandre C.

@Alexandre C .: Tieni a mente n = ln b. Qual è la complessità regolare del modulo per big int? È O (log n log ^ 2 log n)
JoshD

@ JoshD: è qualcosa del genere, penso di aver perso un termine log n, la complessità finale (per l'algoritmo con divisioni) è O (n ^ 2 log ^ 2 n log n) in questo caso.
Alexandre C.

13

Vedi qui .

In particolare questa parte:

Lamé ha mostrato che il numero di passaggi necessari per arrivare al massimo comune divisore per due numeri inferiori a n è

testo alternativo

Quindi O(log min(a, b))è un buon limite superiore.


3
Questo è vero per il numero di passaggi, ma non tiene conto della complessità di ogni passaggio stesso, che scala con il numero di cifre (ln n).
JoshD,

9

Ecco la comprensione intuitiva della complessità di runtime dell'algoritmo di Euclide. Le prove formali sono trattate in vari testi come Introduzione agli algoritmi e TAOCP Vol 2.

Per prima cosa pensa a cosa succede se proviamo a prendere mcd di due numeri di Fibonacci F (k + 1) e F (k). Potresti osservare rapidamente che l'algoritmo di Euclide itera su F (k) e F (k-1). Cioè, con ogni iterazione ci spostiamo verso il basso di un numero nella serie di Fibonacci. Poiché i numeri di Fibonacci sono O (Phi ^ k) dove Phi è il rapporto aureo, possiamo vedere che il tempo di esecuzione di GCD era O (log n) dove n = max (a, b) e log ha base Phi. Successivamente, possiamo dimostrare che questo sarebbe il caso peggiore osservando che i numeri di Fibonacci producono costantemente coppie in cui i resti rimangono abbastanza grandi in ogni iterazione e non diventano mai zero fino a quando non sei arrivato all'inizio della serie.

Possiamo rendere O (log n) dove n = max (a, b) legato ancora più stretto. Supponiamo che b> = a così possiamo scrivere legato a O (log b). Innanzitutto, osserva che MCD (ka, kb) = MCD (a, b). Poiché i valori più grandi di k sono mcd (a, c), possiamo sostituire b con b / gcd (a, b) nel nostro runtime portando a un limite più stretto di O (log b / gcd (a, b)).


Puoi dare una prova formale che i numeri di Fibonacci producono il caso peggiore per l'algoritmo Euclide?
Akash

4

Il caso peggiore di Euclid Algorithm è quando i resti sono i più grandi possibili in ogni passaggio, cioè. per due termini consecutivi della sequenza di Fibonacci.

Quando n e m sono il numero di cifre di aeb, assumendo n> = m, l'algoritmo utilizza divisioni O (m).

Si noti che le complessità sono sempre fornite in termini di dimensioni degli input, in questo caso il numero di cifre.


4

Il caso peggiore si verificherà quando sia n che m sono numeri di Fibonacci consecutivi.

mcd (Fn, Fn − 1) = mcd (Fn − 1, Fn − 2) = ⋯ = mcd (F1, F0) = 1 e l'ennesimo numero di Fibonacci è 1,618 ^ n, dove 1,618 è la sezione aurea.

Quindi, per trovare mcd (n, m), il numero di chiamate ricorsive sarà Θ (logn).


3

Ecco l'analisi nel libro Data Structures and Algorithm Analysis in C di Mark Allen Weiss (seconda edizione, 2.4.4):

L'algoritmo di Euclide funziona calcolando continuamente i resti fino a raggiungere lo 0. L'ultimo resto diverso da zero è la risposta.

Ecco il codice:

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

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

Ecco un TEOREMA che useremo:

Se M> N, allora M mod N <M / 2.

PROVA:

Ci sono due casi. Se N <= M / 2, allora poiché il resto è minore di N, il teorema è vero per questo caso. L'altro caso è N> M / 2. Ma poi N entra in M ​​una volta con un resto M - N <M / 2, dimostrando il teorema.

Quindi, possiamo fare la seguente inferenza:

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

Quindi, dopo due iterazioni, il resto è al massimo la metà del suo valore originale. Ciò dimostrerebbe che il numero di iterazioni è al massimo 2logN = O(logN).

Si noti che l'algoritmo calcola Gcd (M, N), assumendo M> = N. (Se N> M, la prima iterazione del ciclo li scambia.)


2

Il teorema di Gabriel Lame delimita il numero di passi per log (1 / sqrt (5) * (a + 1/2)) - 2, dove la base del logaritmo è (1 + sqrt (5)) / 2. Questo è per lo scenario peggiore per l'algoritmo e si verifica quando gli input sono numeri di Fibanocci consecutivi.

Un limite leggermente più liberale è: log a, dove la base del logaritmo è (sqrt (2)) è implicita in Koblitz.

Per scopi crittografici si considera solitamente la complessità bit per bit degli algoritmi, tenendo conto che la dimensione dei bit è data approssimativamente da k = loga.

Ecco un'analisi dettagliata della complessità bit per bit di Euclid Algorith:

Sebbene nella maggior parte dei riferimenti la complessità bit per bit dell'Algoritmo Euclidico sia data da O (loga) ^ 3, esiste un limite più stretto che è O (loga) ^ 2.

Tener conto di; 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

osservare che: a = r0> = b = r1> r2> r3 ...> rm-1> rm> 0 .......... (1)

e rm è il massimo comune divisore di a e b.

Da un'affermazione nel libro di Koblitz (A course in number Theory and Cryptography) si può dimostrare che: ri + 1 <(ri-1) / 2 ................. ( 2)

Sempre in Koblitz il numero di operazioni di bit richieste per dividere un intero positivo di k bit per un intero positivo di l bit (assumendo k> = l) è dato come: (k-l + 1) .l ...... ............. (3)

Per (1) e (2) il numero di divisoni è O (loga) e quindi per (3) la complessità totale è O (loga) ^ 3.

Ora questo può essere ridotto a O (loga) ^ 2 da un'osservazione in Koblitz.

considera ki = logri +1

per (1) e (2) abbiamo: ki + 1 <= ki per i = 0,1, ..., m-2, m-1 e ki + 2 <= (ki) -1 per i = 0 , 1, ..., m-2

e per (3) il costo totale dei m divisoni è limitato da: SUM [(ki-1) - ((ki) -1))] * ki per i = 0,1,2, .., m

riorganizzare questo: SUM [(ki-1) - ((ki) -1))] * ki <= 4 * k0 ^ 2

Quindi la complessità bit per bit dell'algoritmo di Euclide è O (loga) ^ 2.


1

Per l'algoritmo iterativo, invece, abbiamo:

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

Con le coppie di Fibonacci, non c'è differenza tra iterativeEGCD()e iterativeEGCDForWorstCase()dove quest'ultimo assomiglia al seguente:

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

Sì, con le coppie di Fibonacci, n = a % ned n = a - nè esattamente la stessa cosa.

Sappiamo anche che, in una risposta in precedenza per la stessa domanda, c'è un fattore in diminuzione prevalente: factor = m / (n % m).

Pertanto, per modellare la versione iterativa del GCD euclideo in una forma definita, possiamo raffigurarci come un "simulatore" come questo:

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

Basato sul lavoro (ultima diapositiva) del Dr. Jauhar Ali, il ciclo sopra è logaritmico.

inserisci qui la descrizione dell'immagine

Sì, piccolo Oh perché il simulatore dice al massimo il numero di iterazioni . Le coppie non Fibonacci richiederebbero un numero inferiore di iterazioni rispetto a Fibonacci, se rilevate su GCD euclideo.


Poiché questo studio è stato condotto utilizzando il linguaggio C, i problemi di precisione potrebbero produrre valori errati / imprecisi. Ecco perché è stato utilizzato long long , per adattarsi meglio alla variabile in virgola mobile denominata factor . Il compilatore utilizzato è MinGW 2.95.
Mohamed Ennahdi El Idrissi

1

Ad ogni passo, ci sono due casi

b> = a / 2, quindi a, b = b, a% b renderà b al massimo la metà del suo valore precedente

b <a / 2, allora a, b = b, a% b renderà a al massimo la metà del suo valore precedente, poiché b è minore di a / 2

Quindi, ad ogni passaggio, l'algoritmo ridurrà almeno un numero a almeno la metà in meno.

Nel passaggio al massimo O (log a) + O (log b) , questo sarà ridotto ai casi semplici. Che produce un algoritmo O (log n), dove n è il limite superiore di a e b.

L'ho trovato qui

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.