web-dev-qa-db-fra.com

Comment trouver la complexité temporelle d'un algorithme

La question

Comment trouver la complexité temporelle d'un algorithme?

Qu'ai-je fait avant de poser une question sur SO?

J'ai traversé this , this et beaucoup d'autres liens

Mais nulle part où j'ai pu trouver une explication claire et directe sur la manière de calculer la complexité temporelle.

Que sais-je?

Dites un code aussi simple que celui ci-dessous:

char h = 'y'; // This will be executed 1 time
int abc = 0; // This will be executed 1 time

Dis pour une boucle comme celle ci-dessous:

for (int i = 0; i < N; i++) {        
    Console.Write('Hello World !');
}

int i = 0; Ceci ne sera exécuté qu'une seule fois . Le temps est en fait calculé sur i=0 et non sur la déclaration.

i <N; Ceci sera exécuté N + 1 fois

i ++; Ceci sera exécuté N fois

Donc, le nombre d'opérations requises par cette boucle sont

{1+ (N + 1) + N} = 2N + 2

Remarque: cela peut toujours être faux, car je ne suis pas confiant quant à ma compréhension du calcul de la complexité temporelle.

Ce que je veux savoir?

Ok, je pense que je connais ces petits calculs de base, mais dans la plupart des cas, j’ai vu la complexité du temps comme

O (N), O (n2), O (log n), O (n!) .... et beaucoup autre ,

Quelqu'un peut-il m'aider à comprendre comment calculer la complexité temporelle d'un algorithme? Je suis sûr qu'il y a beaucoup de débutants comme moi qui veulent le savoir.

814
Yasser

Comment trouver la complexité temporelle d'un algorithme

Vous additionnez le nombre d'instructions machine qu'il va exécuter en fonction de la taille de son entrée, puis vous simplifiez l'expression au plus grand terme (lorsque N est très grand) et pouvez inclure tout facteur constant simplificateur.

Par exemple, voyons comment nous simplifions 2N + 2 les instructions machine pour décrire ceci simplement O(N).

Pourquoi enlève-t-on les deux 2s?

Nous nous intéressons à la performance de l'algorithme lorsque N devient grand.

Considérons les deux termes 2N et 2.

Quelle est l'influence relative de ces deux termes lorsque N devient grand? Supposons que N est un million.

Ensuite, le premier terme est 2 millions et le second terme est seulement 2.

Pour cette raison, nous supprimons tous les termes sauf les plus importants pour les grands N.

Nous sommes donc passés de 2N + 2 à 2N.

Traditionnellement, nous ne nous intéressons qu'à la performance jusqu'à des facteurs constants .

Cela signifie que nous ne nous soucions pas vraiment de savoir s'il existe un multiple constant de la différence de performance lorsque N est grand. De toute façon, l'unité de 2N n'est pas bien définie. Nous pouvons donc multiplier ou diviser par un facteur constant pour obtenir l'expression la plus simple.

Ainsi, 2N devient simplement N.

360
Andrew Tomazos

C'est un excellent article: http://www.daniweb.com/software-development/computer-science/threads/13488/time-complexity-of-algorithm

La réponse ci-dessous est copiée d'en haut (au cas où l'excellent lien disparaîtrait)

La métrique la plus courante pour calculer la complexité temporelle est la notation Big O. Cela supprime tous les facteurs constants de sorte que le temps d'exécution puisse être estimé par rapport à N lorsque N s'approche de l'infini. En général, vous pouvez penser comme ça:

statement;

Est constant. La durée d'exécution de l'instruction ne changera pas par rapport à N.

for ( i = 0; i < N; i++ )
     statement;

Est linéaire. La durée d'exécution de la boucle est directement proportionnelle à N. Lorsque N double, il en va de même pour la durée d'exécution.

for ( i = 0; i < N; i++ ) {
  for ( j = 0; j < N; j++ )
    statement;
}

Est quadratique. La durée d'exécution des deux boucles est proportionnelle au carré de N. Lorsque N double, la durée d'exécution augmente de N * N.

while ( low <= high ) {
  mid = ( low + high ) / 2;
  if ( target < list[mid] )
    high = mid - 1;
  else if ( target > list[mid] )
    low = mid + 1;
  else break;
}

Est logarithmique. Le temps d'exécution de l'algorithme est proportionnel au nombre de fois où N peut être divisé par 2. En effet, l'algorithme divise la zone de travail en deux à chaque itération.

void quicksort ( int list[], int left, int right )
{
  int pivot = partition ( list, left, right );
  quicksort ( list, left, pivot - 1 );
  quicksort ( list, pivot + 1, right );
}

Est-ce que N * log (N). Le temps d'exécution est constitué de N boucles (itératives ou récursives) logarithmiques. L'algorithme est donc une combinaison de linéaire et de logarithmique.

En général, faire quelque chose avec chaque article dans une dimension est linéaire, faire quelque chose avec chaque article dans deux dimensions est quadratique, et diviser la zone de travail en deux est logarithmique. Il existe d'autres mesures Big O telles que cubique, exponentielle et racine carrée, mais elles ne sont pas aussi communes. La notation Big O est décrite comme O () où est la mesure. L'algorithme quicksort serait décrit par O (N * log (N)).

Notez que rien de tout cela n'a pris en compte les mesures optimales, moyennes et pires. Chacun aurait sa propre notation Big O. Notez également qu'il s'agit d'une explication TRÈS simpliste. Big O est le plus commun, mais il est aussi plus complexe que je l’ai montré. Il existe également d'autres notations telles que gros oméga, petit o et grand thêta. Vous ne les rencontrerez probablement pas en dehors d'un cours d'analyse algorithmique. ;)

364
Achow

Tiré d'ici - Introduction à la complexité temporelle d'un algorithme

1. Introduction

En informatique, la complexité temporelle d'un algorithme quantifie le temps mis par un algorithme à s'exécuter en fonction de la longueur de la chaîne représentant l'entrée.

2. Big O notation

La complexité temporelle d'un algorithme est généralement exprimée à l'aide de la grande notation O, qui exclut les coefficients et les termes d'ordre inférieur. Lorsqu'elle est exprimée de cette façon, la complexité temporelle est dite asymptotiquement, c'est-à-dire lorsque la taille d'entrée atteint l'infini.

Par exemple, si le temps requis par un algorithme sur toutes les entrées de taille n est au plus 5n3 + 3n, la complexité temporelle asymptotique est O (n3). Plus sur cela plus tard.

Quelques exemples supplémentaires:

  • 1 = O (n)
  • n = O (n2)
  • log (n) = O (n)
  • 2 n + 1 = O (n)

3. O(1) Temps constant:

On dit qu'un algorithme s'exécute en temps constant s'il nécessite le même temps, quelle que soit la taille de l'entrée.

Exemples:

  • tableau: accéder à n'importe quel élément
  • pile de taille fixe: méthodes push et pop
  • file d'attente de taille fixe: méthodes de mise en file d'attente et de retrait de la file d'attente

4. O(n) Temps linéaire

On dit qu'un algorithme s'exécute en temps linéaire si son exécution est directement proportionnelle à la taille d'entrée, c'est-à-dire que le temps augmente linéairement à mesure que la taille d'entrée augmente.

Considérez les exemples suivants. Ci-dessous, je recherche linéairement un élément, sa complexité temporelle est O (n).

int find = 66;
var numbers = new int[] { 33, 435, 36, 37, 43, 45, 66, 656, 2232 };
for (int i = 0; i < numbers.Length - 1; i++)
{
    if(find == numbers[i])
    {
        return;
    }
}

Plus d'exemples:

  • Tableau: Recherche linéaire, parcours, recherche du minimum, etc.
  • ArrayList: contient la méthode
  • File d'attente: contient la méthode

5. O (log n) temps logarithmique:

On dit qu'un algorithme s'exécute en temps logarithmique si son exécution est proportionnelle au logarithme de la taille en entrée.

Exemple: recherche binaire

Rappelez-vous le jeu "vingt questions" - la tâche consiste à deviner la valeur d'un nombre caché dans un intervalle. Chaque fois que vous faites une proposition, on vous dit si votre proposition est trop haute ou trop basse. Jeu de vingt questions implique une stratégie qui utilise votre nombre approximatif pour réduire de moitié la taille de l'intervalle. Ceci est un exemple de la méthode générale de résolution de problème connue sous le nom de recherche binaire

6. O(n2) Temps quadratique

On dit qu'un algorithme s'exécute en temps quadratique si son exécution est proportionnelle au carré de la taille en entrée.

Exemples:

7. Quelques liens utiles

148
Yasser

Bien qu'il y ait de bonnes réponses à cette question. Je voudrais donner une autre réponse ici avec plusieurs exemples de loop.

  • O (n) : la complexité temporelle d'une boucle est considérée comme O (n) si la boucle les variables sont incrémentées/décrémentées d'une quantité constante. Par exemple, les fonctions suivantes ont une complexité temporelle O (n) .

    // Here c is a positive integer constant   
    for (int i = 1; i <= n; i += c) {  
        // some O(1) expressions
    }
    
    for (int i = n; i > 0; i -= c) {
        // some O(1) expressions
    }
    
  • O (n ^ c) : la complexité temporelle des boucles imbriquées est égale au nombre de fois que l'instruction la plus interne est exécutée. Par exemple, les exemples de boucles suivants ont une complexité temporelle O (n ^ 2)

    for (int i = 1; i <=n; i += c) {
       for (int j = 1; j <=n; j += c) {
          // some O(1) expressions
       }
    }
    
    for (int i = n; i > 0; i += c) {
       for (int j = i+1; j <=n; j += c) {
          // some O(1) expressions
    }
    

    Par exemple, le tri par sélection et le tri par insertion ont une complexité temporelle O (n ^ 2) .

  • O (Logn) Temps La complexité d'une boucle est considérée comme O (Logn) si les variables de la boucle est divisé/multiplié par un montant constant.

    for (int i = 1; i <=n; i *= c) {
       // some O(1) expressions
    }
    for (int i = n; i > 0; i /= c) {
       // some O(1) expressions
    }
    

    Par exemple, la recherche binaire a une complexité temporelle O (Logn) .

  • O (LogLogn) Temps La complexité d'une boucle est considérée comme O (LogLogn) si les variables de la boucle est réduit/augmenté de manière exponentielle par une quantité constante.

    // Here c is a constant greater than 1   
    for (int i = 2; i <=n; i = pow(i, c)) { 
       // some O(1) expressions
    }
    //Here fun is sqrt or cuberoot or any other constant root
    for (int i = n; i > 0; i = fun(i)) { 
       // some O(1) expressions
    }
    

Un exemple d'analyse de la complexité temporelle

int fun(int n)
{    
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j < n; j += i)
        {
            // Some O(1) task
        }
    }    
}

Analyse :

For i = 1, the inner loop is executed n times. For i = 2, the inner loop is executed approximately n/2 times. For i = 3, the inner loop is executed approximately n/3 times. For i = 4, the inner loop is executed approximately n/4 times. ……………………………………………………. For i = n, the inner loop is executed approximately n/n times.

Donc la complexité temporelle totale de l'algorithme ci-dessus est (n + n/2 + n/3 + … + n/n), qui devient n * (1/1 + 1/2 + 1/3 + … + 1/n)

L’important dans la série (1/1 + 1/2 + 1/3 + … + 1/n) est égal à O (Logn) . Donc, la complexité temporelle du code ci-dessus est O (nLogn) .


Réf: 12

87
zangw

Complexité temporelle avec exemples

1 - Opérations de base (arithmétique, comparaisons, accès aux éléments d’un tableau, affectation): le temps d’exécution est toujours constant O (1)

Exemple :

read(x)                               // O(1)
a = 10;                               // O(1)
a = 1.000.000.000.000.000.000         // O(1)

2 - Instruction if else: ne prend que le temps d'exécution maximal de deux ou plusieurs instructions possibles.

Exemple:

age = read(x)                               // (1+1) = 2
if age < 17 then begin                      // 1
      status = "Not allowed!";              // 1
end else begin
      status = "Welcome! Please come in";   // 1
      visitors = visitors + 1;              // 1+1 = 2
end;

Ainsi, la complexité du pseudo-code ci-dessus est T(n) = 2 + 1 + max (1, 1 + 2) = 6. Ainsi, son grand oh est toujours constant T(n) = O (1).

3 - Boucle (pour, tant que, répéter): Le temps d'exécution de cette instruction est le nombre de boucles multiplié par le nombre d'opérations à l'intérieur de ces boucles.

Exemple:

total = 0;                                  // 1
for i = 1 to n do begin                     // (1+1)*n = 2n
      total = total + i;                    // (1+1)*n = 2n
end;
writeln(total);                             // 1

Donc, sa complexité est T(n) = 1 + 4n + 1 = 4n + 2. Ainsi, T(n) = O (n).

4 - Boucle imbriquée (en boucle à l'intérieur de la boucle): Puisqu'il y a au moins une boucle à l'intérieur de la boucle principale, le temps d'exécution de cette instruction a utilisé O (n ^ 2) ou O (n ^ 3).

Exemple:

for i = 1 to n do begin                     // (1+1)*n  = 2n
   for j = 1 to n do begin                  // (1+1)n*n = 2n^2
       x = x + 1;                           // (1+1)n*n = 2n^2
       print(x);                            // (n*n)    = n^2
   end;
end;

Temps courant commun

L'analyse d'un algorithme présente des temps d'exécution courants:

  1. O (1) - Temps constant Le temps constant signifie que le temps d’exécution est constant, il ( n’est pas affecté par la taille en entrée .

  2. O (n) - Temps linéaire Lorsqu'un algorithme accepte n taille d'entrée, il effectue également n opérations.

  3. O (log n) - L'algorithme logarithmique temporel dont le temps d'exécution est O (log n) est légèrement plus rapide que O (n). Généralement, l'algorithme divise le problème en sous-problèmes de même taille. Exemple: algorithme de recherche binaire, algorithme de conversion binaire.

  4. O (n log n) - Temps linéarithmique Ce temps d'exécution est souvent trouvé dans les "algorithmes diviser et conquérir" qui divisent le problème en sous problèmes de manière récursive, puis les fusionnent en n temps. Exemple: algorithme de tri par fusion.

  5. Sur2) - Algorithme Quadratic Time Look Bubble Sort!

  6. Sur3) - Heure cubique Il a le même principe avec O (n2).

  7. O (2n) - Temps exponentiel Il est très lent lorsque l'entrée augmente, si n = 1 000 000, T(n) serait 21 000 000. L'algorithme Brute Force a cette durée.

  8. O(n!) – Factorial Time THE SLOWEST !!! Example : Travel Salesman Problem (TSP)

Tiré de cet article . Très bien expliqué devrait donner une lecture.

69
Yasser

Lorsque vous analysez du code, vous devez l’analyser ligne par ligne, en comptant chaque opération/en reconnaissant la complexité temporelle. À la fin, vous devez le résumer pour obtenir une image complète.

Par exemple, vous pouvez avoir une boucle simple avec complexité linéaire , mais plus tard dans ce même programme, vous pouvez avoir une triple boucle qui a complexité cubique , votre programme aura donc complexité cubique. L'ordre de croissance des fonctions entre en jeu ici.

Regardons quelles sont les possibilités de complexité temporelle d'un algorithme, vous pouvez voir l'ordre de croissance que j'ai mentionné ci-dessus:

  • Temps constant a un ordre de croissance 1, par exemple: a = b + c.

  • Le temps logarithmique a un ordre de croissance LogN, il se produit généralement lorsque vous divisez quelque chose en deux ( recherche binaire, arbres, voire boucles), ou multiplier quelque chose de la même manière.

  • Linéaire , l'ordre de croissance est N, par exemple

    int p = 0;
    for (int i = 1; i < N; i++)
      p = p + 2;
    
  • Linéarithmique , l'ordre de croissance est n*logN, il se produit généralement dans les algorithmes de division et de conquête.

  • Cubic , ordre de croissance N^3, l'exemple classique est une boucle triple où vous vérifiez tous les triplets:

    int x = 0;
    for (int i = 0; i < N; i++)
       for (int j = 0; j < N; j++)
          for (int k = 0; k < N; k++)
              x = x + 2
    
  • Exponentiel , ordre de croissance 2^N, se produit généralement lorsque vous effectuez une recherche exhaustive, par exemple, vérifiez des sous-ensembles de ensemble.

40

En termes simples, la complexité temporelle est un moyen de résumer comment le nombre d'opérations ou la durée d'exécution d'un algorithme augmente à mesure que la taille d'entrée augmente.

Comme la plupart des choses dans la vie, un cocktail peut nous aider à comprendre.

O (N)

Lorsque vous arrivez à la fête, vous devez serrer la main de tout le monde (faire une opération sur chaque article). Au fur et à mesure que le nombre de participants N augmente, le temps/travail nécessaire pour serrer la main de tous les autres augmente à mesure que O(N).

Pourquoi O(N) et non cN?

La quantité de temps nécessaire pour serrer la main des gens varie. Vous pouvez faire la moyenne de ceci et le capturer dans une constante c. Mais l'opération fondamentale ici - serrer la main de tout le monde - serait toujours proportionnelle à O(N), peu importe ce que c était. Lorsque nous discutons de la question de savoir si nous devrions aller à un cocktail, nous sommes souvent plus intéressés par le fait que nous devrons rencontrer tout le monde que par les détails minutieux de ces réunions.

O (N ^ 2)

L'animateur du cocktail souhaite que vous jouiez à un jeu idiot où tout le monde se rencontre. Par conséquent, vous devez rencontrer N-1 autres personnes et, comme la personne suivante vous a déjà rencontrés, elles doivent rencontrer N-2 personnes, etc. La somme de cette série est x^2/2+x/2. À mesure que le nombre de participants augmente, le terme x^2 grossit rapidement . Nous omettons donc tout le reste.

O (N ^ 3)

Vous devez rencontrer tout le monde et, lors de chaque réunion, vous devez parler de tout le monde dans la salle.

O (1)

L'hôte veut annoncer quelque chose. Ils ding un verre à vin et parlent fort. Tout le monde les entend. Il s’avère que peu importe le nombre de participants, cette opération prend toujours le même temps.

O (log N)

L'hôte a disposé tout le monde à la table dans l'ordre alphabétique. Où est Dan? Vous pensez qu'il doit être quelque part entre Adam et Mandy (certainement pas entre Mandy et Zach!). Compte tenu de cela, est-il entre George et Mandy? Non, il doit être entre Adam et Fred, et entre Cindy et Fred. Et ainsi de suite ... nous pouvons localiser efficacement Dan en examinant la moitié de l'ensemble, puis la moitié de cet ensemble. En fin de compte, nous examinons les individus O (log_2 N) .

O (N log N)

Vous pouvez trouver où vous asseoir à la table en utilisant l'algorithme ci-dessus. Si un grand nombre de personnes se présentaient à la table, une à la fois et que tout le monde le faisait, cela prendrait O (N log N) temps. Cela se révèle être le temps qu'il faut pour trier une collection d'éléments quand ils doivent être comparés.

Meilleur/pire cas

Vous arrivez à la fête et vous devez trouver Inigo - combien de temps cela prendra-t-il? Cela dépend du moment de votre arrivée. Si tout le monde bouge, vous avez atteint le pire des cas: cela prendra O(N) temps. Cependant, si tout le monde est assis à la table, cela ne prendra que O(log N) temps. Ou peut-être pouvez-vous exploiter le pouvoir de cri de verre à vin de l'hôte et cela ne prendra que O(1) temps.

En supposant que l'hôte soit indisponible, nous pouvons dire que l'algorithme de recherche Inigo a une limite inférieure de O(log N) et une limite supérieure de O(N), en fonction de l'état de la partie à votre arrivée.

Espace et communication

Les mêmes idées peuvent être appliquées pour comprendre comment les algorithmes utilisent l'espace ou la communication.

Knuth a écrit un article de Nice sur l’ancien intitulé "La complexité des chansons" .

Théorème 2: Il existe des chansons arbitrairement longues de complexité O (1).

PREUVE: (à cause de Casey et de la Sunshine Band). Considérons les chansons Sk définies par (15), mais avec

V_k = 'That's the way,' U 'I like it, ' U
U   = 'uh huh,' 'uh huh'

pour tous k.

32
Richard

Je sais que cette question remonte dans le temps et que d’excellentes réponses sont proposées ici, mais j’aimerais néanmoins partager un autre aspect pour les personnes qui ont l’esprit mathématique et qui trébucheront dans cet article. Le théorème maître est une autre chose utile à connaître lors de l'étude de la complexité. Je ne l'ai pas vu mentionné dans les autres réponses.

4
Gentian Kasa

O (n) est la grande notation O utilisée pour écrire la complexité temporelle d'un algorithme. Lorsque vous additionnez le nombre d'exécutions dans un algorithme, vous obtenez une expression de résultat telle que 2N + 2, dans cette expression, N est le terme dominant (terme ayant le plus grand effet sur l'expression si sa valeur augmente ou diminue). Maintenant, O(N) est la complexité temporelle alors que N est le terme dominant. Exemple

For i= 1 to n;
  j= 0;
while(j<=n);
  j=j+1;

ici le nombre total d'exécutions pour la boucle interne est n + 1 et le nombre total d'exécutions pour la boucle externe est n (n + 1)/2, le nombre total d'exécutions pour l'algorithme total est donc n + 1 + n (n + 1/2 ) = (n ^ 2 + 3n)/2. ici n ^ 2 est le terme dominant et la complexité temporelle de cet algorithme est donc O (n ^ 2)

2
ifra khan