web-dev-qa-db-fra.com

Complexité temporelle de l'algorithme d'Euclide

J'ai du mal à décider de la complexité temporelle du plus grand algorithme de dénominateur commun d'Euclid. Cet algorithme en pseudo-code est:

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

Cela semble dépendre de a et b . Je pense que la complexité temporelle est O (a% b). Est-ce exact? Y a-t-il une meilleure façon d'écrire ça?

76
Donald Taylor

Une astuce pour analyser la complexité temporelle de l'algorithme d'Euclid consiste à suivre ce qui se passe sur deux itérations:

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

Maintenant, a et b vont tous deux diminuer, au lieu d'un seul, ce qui facilite l'analyse. Vous pouvez le diviser en cas:

  • Minuscule A: 2a <= b
  • Minuscule B: 2b <= a
  • Petit A: 2a > b mais a < b
  • Petit B: 2b > a mais b < a
  • Egal: a == b

Maintenant, nous allons montrer que chaque cas réduit le a+b total d'au moins un quart:

  • Tiny A: b % (a % b) < a et 2a <= b, donc b est diminué d'au moins la moitié, donc a+b diminué d'au moins 25%
  • Minuscule B: a % b < b et 2b <= a, donc a est diminué d'au moins la moitié, donc a+b diminué d'au moins 25%
  • Petit A: b deviendra b-a, ce qui est inférieur à b/2, diminuant a+b d'au moins 25%.
  • Petit B: a deviendra a-b, ce qui est inférieur à a/2, diminuant a+b d'au moins 25%.
  • Egal: a+b passe à 0, ce qui diminue évidemment a+b d'au moins 25%.

Par conséquent, lors de l'analyse de cas, chaque étape double diminue a+b d'au moins 25%. Il y a un nombre maximal de fois que cela peut se produire avant que a+b ne soit obligé de descendre en dessous de 1. Le nombre total d'étapes (S) jusqu'à ce que vous atteigniez 0 doit satisfaire (4/3)^S <= A+B. Maintenant, juste le travailler:

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

Le nombre d'itérations est donc linéaire par rapport au nombre de chiffres saisis. Pour les nombres qui entrent dans les registres du processeur, il est raisonnable de modéliser les itérations comme prenant un temps constant et de prétendre que le temps d'exécution total du gcd est linéaire.

Bien sûr, si vous avez affaire à de grands entiers, vous devez tenir compte du fait que les opérations de module au sein de chaque itération n’ont pas de coût constant. Grosso modo, le temps total d'exécution asymptotique sera égal à 2 fois plus qu'un facteur polylogarithmique. Quelque chose commen^2 lg(n) 2^O(log* n). Le facteur polylogarithmique peut être évité en utilisant plutôt un binary gcd .

59
Craig Gidney

Le moyen approprié d'analyser un algorithme consiste à déterminer ses scénarios les plus défavorables . Le pire cas de GCD euclidien se produit lorsque des paires de Fibonacci sont impliquées .void EGCD(fib[i], fib[i - 1]), où i> 0.

Par exemple, optons pour le cas où le dividende est de 55 et le diviseur est de 34 (rappelons que nous avons toujours affaire à des nombres de fibonacci).

enter image description here

Comme vous pouvez le constater, cette opération a coûté 8 itérations (ou appels récursifs).

Essayons des nombres de Fibonacci plus grands, à savoir 121393 et ​​75025. Nous pouvons également remarquer ici qu'il a fallu 24 itérations (ou appels récursifs).

enter image description here

Vous pouvez également remarquer que chaque itération donne un numéro de Fibonacci. C'est pourquoi nous avons tant d'opérations. Nous ne pouvons obtenir des résultats similaires qu'avec les chiffres de Fibonacci.

Par conséquent, la complexité temporelle va être représentée par un petit Oh (limite supérieure), cette fois. La limite inférieure est intuitivement Omega (1): cas de 500 divisé par 2, par exemple.

Résoudons la relation de récurrence:

enter image description here

Nous pouvons dire alors que le GCD euclidien peut effectuer une opération log (xy) au plus .

Il y a un bon coup d'oeil sur cet article de wikipedia .

Il a même une belle parcelle de complexité pour les paires de valeur.

Ce n'est pas O(a%b)

Il est connu (voir article) qu'il ne faudra jamais plus de pas cinq fois plus nombreux que le nombre le plus petit. Ainsi, le nombre maximal d'étapes augmente avec le nombre de chiffres (ln b). Le coût de chaque étape augmente également avec le nombre de chiffres. La complexité est donc liée à O(ln^2 b), où b est le nombre le plus petit. C'est une limite supérieure et le temps réel est généralement inférieur.

16
JoshD

Voir ici .

En particulier cette partie:

Lamé a montré que le nombre de pas nécessaires pour arriver au plus grand commun diviseur pour deux nombres inférieurs à n est 

alt text

Donc, O(log min(a, b)) est une bonne limite supérieure.

11
IVlad

Voici une compréhension intuitive de la complexité d'exécution de l'algorithme d'Euclid. Les preuves formelles sont couvertes dans divers textes tels que Introduction to Algorithms et TAOCP Vol 2.

D'abord, réfléchissez à ce que nous ferions si nous essayions de prendre gcd de deux nombres de Fibonacci, F (k + 1) et F (k). Vous remarquerez peut-être rapidement que l'algorithme d'Euclide effectue une itération sur F(k) et F (k-1). C'est-à-dire qu'à chaque itération, nous diminuons d'un numéro dans la série de Fibonacci. Comme les nombres de Fibonacci sont O (Phi ^ k), où Phi est le nombre d'or, nous pouvons voir que le temps d'exécution de GCD était O (log n) où n = max (a, b) et le journal a la base de Phi. Ensuite, nous pouvons prouver que ce serait le pire des cas en observant que les nombres de Fibonacci produisent systématiquement des paires où les restes restent assez importants à chaque itération et ne deviennent jamais nuls tant que vous n'êtes pas arrivé au début de la série.

Nous pouvons rendre O (log n) où n = max (a, b) lié encore plus étroitement. Supposons que b> = a afin que nous puissions écrire lié en O (log b). Tout d'abord, observez que GCD (ka, kb) = GCD (a, b). Comme la plus grande valeur de k est gcd (a, c), nous pouvons remplacer b par b/gcd (a, b) dans notre exécution, ce qui conduit à une liaison plus étroite de O (log b/gcd (a, b)).

8
Shital Shah

Le pire cas d’algorithme d’Euclide est celui où les restes sont les plus grands possibles à chaque étape, c’est-à-dire. pendant deux termes consécutifs de la séquence de Fibonacci.

Lorsque n et m sont le nombre de chiffres de a et b, en supposant que n> = m, l'algorithme utilise O(m) divisions.

Notez que les complexités sont toujours données en termes de tailles des entrées, dans ce cas, le nombre de chiffres.

4
Alexandre C.

Le cas le plus défavorable se produira lorsque n et m sont tous deux des nombres de Fibonacci consécutifs.

gcd (Fn, Fn − 1) = gcd (Fn − 1, Fn − 2) = = gcd (F1, F0) = 1 et le nième nombre de Fibonacci est égal à 1.618 ^ n, où 1.618 est le rapport Golden.

Donc, pour trouver gcd (n, m), le nombre d'appels récursifs sera Θ (logn).

2
Arnav Attri

Pour l'algorithme itératif, cependant, nous avons:

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

Avec les paires de Fibonacci, il n'y a pas de différence entre iterativeEGCD() et iterativeEGCDForWorstCase(), cette dernière ressemblant à ceci:

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

Oui, avec Fibonacci Pairs, n = a % n et n = a - n, c’est exactement la même chose.

Nous savons également que, dans une réponse précédente à la même question, il existait un facteur décroissant: factor = m / (n % m).

Par conséquent, pour façonner la version itérative du GCD euclidien sous une forme définie, nous pouvons décrire comme un "simulateur" comme ceci:

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

Sur la base du work (dernière diapositive) du Dr Jauhar ALi, la boucle ci-dessus est logarithmique.

enter image description here

Oui, petit Oh parce que le simulateur indique le nombre d'itérations au plus. Les paires non Fibonacci prendraient un nombre d'itérations inférieur à celui de Fibonacci, lorsqu'elles seraient interrogées sur le GCD euclidien.

Le théorème de Gabriel Lame limite le nombre d'étapes par log (1/sqrt (5) * (a + 1/2)) - 2, où la base du journal est (1 + sqrt (5))/2. C'est le scénario le plus défavorable pour l'algorithme et il se produit lorsque les entrées sont des nombres de Fibanocci consécutifs.

Une borne légèrement plus libérale est: log a, où la base du journal est (sqrt (2)) est impliquée par Koblitz.

Pour des raisons cryptographiques, on considère généralement la complexité au niveau des bits des algorithmes, en tenant compte du fait que la taille des bits est donnée approximativement par k = loga.

Voici une analyse détaillée de la complexité au niveau du bit de Euclid Algorith:

Bien que dans la plupart des références, la complexité au niveau des bits de Euclid Algorithm soit donnée par O (loga) ^ 3, il existe une limite plus étroite qui est O (loga) ^ 2.

Considérer; 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

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

et rm est le plus grand commun diviseur de a et b.

Dans le livre de Koblitz (Un cours de théorie des nombres et de cryptographie), on peut prouver que: ri + 1 <(ri-1)/2 ................. ( 2)

Toujours dans Koblitz, le nombre d'opérations binaires nécessaires pour diviser un entier positif de k bits par un entier positif de l bits (en supposant que k> = l) est donné par: (k-l + 1) .l ...... ............. (3)

Par (1) et (2), le nombre de divisions est O(loga) et donc par (3), la complexité totale est égale à O (loga) ^ 3.

Maintenant ceci peut être réduit à O (loga) ^ 2 par une remarque dans Koblitz.

considérer ki = logri +1

par (1) et (2) on a: ki + 1 <= ki pour i = 0,1, ..., m-2, m-1 et ki + 2 <= (ki) -1 pour i = 0 , 1, ..., m-2

et par (3) le coût total des m divisions est limité par: SUM [(ki-1) - ((ki) -1))] * ki pour i = 0,1,2, .., m

réorganiser ceci: SUM [(ki-1) - ((ki) -1))] * ki <= 4 * k0 ^ 2

La complexité au niveau des bits de l'algorithme d'Euclid est donc O (loga) ^ 2.

0
esra