380 votes

Détermination de la complexité pour les fonctions récursives (notation Big O)

J'ai un partiel d'informatique demain et j'ai besoin d'aide pour déterminer la complexité de ces fonctions récursives. Je sais comment résoudre des cas simples, mais j'essaie toujours d'apprendre comment résoudre ces cas plus difficiles. Ce ne sont que quelques exemples de problèmes que je n'ai pas pu résoudre. Toute aide serait très appréciée et m'aiderait grandement dans mes études, merci !

int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

518voto

coder Points 1928

La complexité temporelle, en notation Big O, pour chaque fonction :


int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

Cette fonction est appelée récursivement n fois avant d'atteindre le cas de base, donc son O(n) souvent appelé linéaire .


int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

Cette fonction est appelée n-5 à chaque fois, donc nous déduisons cinq de n avant d'appeler la fonction, mais n-5 est également O(n) . (En fait, on parle d'un ordre de n/5 fois. Et, O(n/5) = O(n) ).


int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

Cette fonction est log(n) base 5, pour chaque fois que nous divisons par 5 avant d'appeler la fonction pour que son O(log(n)) (base 5), souvent appelé logarithmique et le plus souvent, la notation Big O et l'analyse de la complexité utilisent la base 2.


void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

Ici, c'est O(2^n) o exponentielle puisque chaque appel de fonction s'appelle lui-même deux fois, à moins qu'il n'ait été recouru. n temps.


int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

Et ici la boucle for prend n/2 puisque nous augmentons par 2, et la récursion prend n/5 et puisque la boucle for est appelée récursivement, donc, la complexité temporelle est en

(n/5) * (n/2) = n^2/10,

en raison du comportement asymptotique et des considérations relatives au scénario le plus défavorable ou de la limite supérieure que le grand O s'efforce d'atteindre, nous ne nous intéressons qu'au plus grand terme, de sorte que O(n^2) .


Bonne chance pour tes examens de mi-session ;)

144voto

nhahtdh Points 28167

Pour le cas où n <= 0 , T(n) = O(1) . Par conséquent, la complexité temporelle dépendra du moment où n >= 0 .

Nous allons examiner le cas n >= 0 dans la partie ci-dessous.

1.

T(n) = a + T(n - 1)

où a est une constante.

Par induction :

T(n) = n * a + T(0) = n * a + b = O(n)

où a, b sont des constantes.

2.

T(n) = a + T(n - 5)

où a est une constante

Par induction :

T(n) = ceil(n / 5) * a + T(k) = ceil(n / 5) * a + b = O(n)

où a, b sont des constantes et k <= 0

3.

T(n) = a + T(n / 5)

où a est une constante

Par induction :

T(n) = a * log5(n) + T(0) = a * log5(n) + b = O(log n)

où a, b sont des constantes

4.

T(n) = a + 2 * T(n - 1)

où a est une constante

Par induction :

T(n) = a + 2a + 4a + ... + 2^(n-1) * a + T(0) * 2^n 
     = a * 2^n - a + b * 2^n
     = (a + b) * 2^n - a
     = O(2 ^ n)

où a, b sont des constantes.

5.

T(n) = n / 2 + T(n - 5)

où n est une constante

Réécriture n = 5q + r où q et r sont des entiers et r = 0, 1, 2, 3, 4

T(5q + r) = (5q + r) / 2 + T(5 * (q - 1) + r)

Nous avons q = (n - r) / 5 et comme r < 5, on peut le considérer comme une constante, donc q = O(n)

Par induction :

T(n) = T(5q + r)
     = (5q + r) / 2 + (5 * (q - 1) + r) / 2 + ... + r / 2 +  T(r)
     = 5 / 2 * (q + (q - 1) + ... + 1) +  1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * (q + 1) * q + 1 / 2 * (q + 1) * r + T(r)
     = 5 / 4 * q^2 + 5 / 4 * q + 1 / 2 * q * r + 1 / 2 * r + T(r)

Puisque r < 4, nous pouvons trouver une certaine constante b de sorte que b >= T(r)

T(n) = T(5q + r)
     = 5 / 2 * q^2 + (5 / 4 + 1 / 2 * r) * q + 1 / 2 * r + b
     = 5 / 2 * O(n ^ 2) + (5 / 4 + 1 / 2 * r) * O(n) + 1 / 2 * r + b
     = O(n ^ 2)

43voto

Shubham Points 24

L'un des meilleurs moyens que j'ai trouvé pour estimer la complexité d'un algorithme récursif est de dessiner l'arbre de récursion. Une fois que vous avez l'arbre récursif :

Complexity = length of tree from root node to leaf node * number of leaf nodes
  1. La première fonction aura une longueur de n et le nombre de nœud de feuille 1 la complexité sera donc n*1 = n

  2. La deuxième fonction aura la longueur de n/5 et le nombre de nœuds feuilles à nouveau 1 la complexité sera donc n/5 * 1 = n/5 . Elle devrait être approximée à n

  3. Pour la troisième fonction, puisque n est divisé par 5 à chaque appel récursif, la longueur de l'arbre récursif sera log(n)(base 5) et le nombre de nœuds feuilles est de nouveau de 1, donc la complexité sera de log(n)(base 5) * 1 = log(n)(base 5)

  4. Pour la quatrième fonction, puisque chaque nœud aura deux nœuds enfants, le nombre de nœuds feuilles sera égal à (2^n) et la longueur de l'arbre récursif sera de n la complexité sera donc (2^n) * n . Mais comme n est insignifiant devant (2^n) on peut l'ignorer et dire que la complexité n'est pas un problème. (2^n) .

  5. Pour la cinquième fonction, il y a deux éléments qui introduisent la complexité. Complexité introduite par la nature récursive de la fonction et complexité introduite par for boucle dans chaque fonction. En faisant le calcul ci-dessus, la complexité introduite par la nature récursive de la fonction sera de ~ n et la complexité due à la boucle for n . La complexité totale sera de n*n .

Note : Il s'agit d'une méthode rapide et sale de calcul de la complexité (rien d'officiel !). Nous serions ravis d'entendre vos commentaires à ce sujet. Merci.

20voto

OhadM Points 2397

Nous pouvons le prouver mathématiquement, ce qui est quelque chose qui m'a échappé dans les réponses ci-dessus.

Il peut dramatiquement vous aider à comprendre comment calculer n'importe quelle méthode. Je vous recommande de le lire de haut en bas pour bien comprendre comment procéder :

  1. T(n) = T(n-1) + 1 Cela signifie que le temps nécessaire pour que la méthode se termine est égal à la même méthode mais avec n-1, soit T(n-1) et nous ajoutons maintenant + 1 car c'est le temps qu'il faut pour que les opérations générales soient achevées (sauf T(n-1) ). Maintenant, nous allons trouver T(n-1) comme suit : T(n-1) = T(n-1-1) + 1 . Il semble que nous pouvons maintenant former une fonction qui peut nous donner une sorte de répétition pour que nous puissions bien comprendre. Nous allons placer le côté droit de T(n-1) = ... au lieu de T(n-1) dans la méthode T(n) = ... qui nous donnera : T(n) = T(n-1-1) + 1 + 1 qui est T(n) = T(n-2) + 2 ou en d'autres termes, nous devons trouver notre manquant k : T(n) = T(n-k) + k . L'étape suivante consiste à prendre n-k et prétendent que n-k = 1 car à la fin de la récursion, il faudra exactement O(1) lorsque n<=0 . A partir de cette simple équation, nous savons maintenant que k = n - 1 . Plaçons k dans notre méthode finale : T(n) = T(n-k) + k qui nous donnera : T(n) = 1 + n - 1 ce qui est exactement n o O(n) .
  2. C'est la même chose que 1. Vous pouvez le tester vous-même et voir que vous obtenez O(n) .
  3. T(n) = T(n/5) + 1 comme précédemment, le temps de finition de cette méthode est égal au temps de la même méthode mais avec n/5 c'est pourquoi elle est limitée à T(n/5) . Trouvons T(n/5) comme en 1 : T(n/5) = T(n/5/5) + 1 qui est T(n/5) = T(n/5^2) + 1 . Plaçons T(n/5) à l'intérieur de T(n) pour le calcul final : T(n) = T(n/5^k) + k . Encore une fois, comme avant, n/5^k = 1 qui est n = 5^k ce qui est exactement comme demander ce qui en puissance de 5, nous donnera n, la réponse est log5n = k (logarithme de la base 5). Plaçons nos résultats dans T(n) = T(n/5^k) + k comme suit : T(n) = 1 + logn qui est O(logn)
  4. T(n) = 2T(n-1) + 1 ce que nous avons ici est fondamentalement le même que précédemment, mais cette fois nous invoquons la méthode récursivement 2 fois, donc nous la multiplions par 2. trouvons T(n-1) = 2T(n-1-1) + 1 qui est T(n-1) = 2T(n-2) + 1 . Comme précédemment, plaçons notre trouvaille : T(n) = 2(2T(n-2)) + 1 + 1 qui est T(n) = 2^2T(n-2) + 2 qui nous donne T(n) = 2^kT(n-k) + k . Trouvons k en affirmant que n-k = 1 qui est k = n - 1 . Plaçons k comme suit : T(n) = 2^(n-1) + n - 1 ce qui correspond à peu près à O(2^n)
  5. T(n) = T(n-5) + n + 1 C'est presque la même chose que le 4, mais maintenant nous ajoutons n parce que nous en avons un for boucle. Trouvons T(n-5) = T(n-5-5) + n + 1 qui est T(n-5) = T(n - 2*5) + n + 1 . Plaçons-le : T(n) = T(n-2*5) + n + n + 1 + 1) qui est T(n) = T(n-2*5) + 2n + 2) et pour le k : T(n) = T(n-k*5) + kn + k) encore : n-5k = 1 qui est n = 5k + 1 c'est-à-dire à peu près n = k . Cela nous donnera : T(n) = T(0) + n^2 + n ce qui correspond à peu près à O(n^2) .

Je vous recommande maintenant de lire le reste des réponses qui vous donneront une meilleure perspective. Bonne chance pour gagner ces grands O :)

11voto

higlu Points 334

La clé ici est de visualiser l'arbre d'appel. Une fois cela fait, la complexité est :

nodes of the call tree * complexity of other code in the function

le dernier terme peut être calculé de la même manière que pour une fonction itérative normale.

Au lieu de cela, le nombre total de nœuds d'un arbre complet est calculé comme suit

                  C^L - 1
                  -------  , when C>1
               /   C - 1
              /
 # of nodes =
              \    
               \ 
                  L        , when C=1 (this is special case of a single branch tree)

Où C est le nombre d'enfants de chaque nœud et L est le nombre de niveaux de l'arbre (racine comprise).

Il est facile de visualiser l'arbre. Partez du premier appel (nœud racine) puis dessinez un nombre d'enfants égal au nombre d'appels récursifs de la fonction. Il est également utile d'écrire le paramètre passé à la sous-appel comme "valeur du noeud".

Voici donc les résultats pour les exemples ci-dessus :


int recursiveFun1(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun1(n-1);
}

Pensez d'abord à l'arbre d'appel :

n     level 1
n-1   level 2
n-2   level 3
n-3   level 4
... ~ n levels -> L = n

Ici, le nombre d'enfants par nœud est C = 1, et le nombre de niveaux L = n+1. La complexité du reste de la fonction est O(1). Par conséquent, la complexité totale est L * O(1) = (n+1) * O(1) = (n+1) * O(1). O(n)


int recursiveFun2(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun2(n-5);
}

L'arbre d'appel est ici :

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

De nouveau C = 1, mais L = n/5. La complexité du reste de la fonction est O(1). Par conséquent, la complexité totale est L * O(1) = (n/5) * O(1) = (n/5) * O(1). O(n)


int recursiveFun3(int n)
{
    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun3(n/5);
}

L'arbre d'appel est :

n
n/5
n/5^2
n/5^3
... ~ log5(n) levels -> L = log5(n)

Donc C = 1, L = log(n). La complexité du reste de la fonction est O(1). Donc la complexité totale est L * O(1) = log5(n) * O(1) = O(log(n))


void recursiveFun4(int n, int m, int o)
{
    if (n <= 0)
    {
        printf("%d, %d\n",m, o);
    }
    else
    {
        recursiveFun4(n-1, m+1, o);
        recursiveFun4(n-1, m, o+1);
    }
}

Ici, l'arbre d'appel est plus complexe :

               n                   level 1
      n-1             n-1          level 2
  n-2     n-2     n-2     n-2      ...
n-3 n-3 n-3 n-3 n-3 n-3 n-3 n-3    ...     
              ...                ~ n levels -> L = n

Ici, le nombre d'enfants par nœud est C = 2, tandis que L = n. La complexité du reste de la fonction est O(1). Cette fois, nous utilisons la formule complète pour le nombre de noeuds dans l'arbre d'appel car C > 1. Par conséquent, la complexité totale est de (C^L-1)/(C-1) * O(1) = (2^n - 1) * O(1) = (2^n - 1) * O(1) O(2^n) .


int recursiveFun5(int n)
{
    for (i = 0; i < n; i += 2) {
        // do something
    }

    if (n <= 0)
        return 1;
    else
        return 1 + recursiveFun5(n-5);
}

Encore une fois, l'arbre d'appel est :

n
n-5
n-10
n-15
... ~ n/5 levels -> L = n/5

Ici C = 1, L = n/5. La complexité du reste de la fonction est O(n). Donc la complexité totale est L * O(1) = (n/5) * O(n) = O(n^2)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X