Reconnaissance d'un tableau trié

Définissions le problème de décision associé au problème de savoir si OUI ou NON un tableau d'entiers est trié :

Problème de décision

  • Nom : est trié ?
  • Entrée : un tableau $T$ d'entiers
  • Question : $T$ est-il trié de façon croissante ?

Algorithme

Il existe un algorithme très simple pour le résoudre :

algorithme est_trie(T: [entier])  booléen:
    pour chaque (i := entier) de [1 .. T.longueur[:
        si T[i] < T[i-1]:
            rendre Faux
    rendre Vrai

Tests de Fonctionnement

L'algorithme rend bien :

Preuve

La finitude de l'algorithme est claire puisqu'il n'y a qu'une boucle for avec autant d'itérations que la taille du tableau passé en paramètre.

La preuve de correction est tout aussi évidente. Si on arrive en ligne 5 c'est que $T[i-1] \leq T[i]$ pour tout $i \in [1, T.\mbox{\small longueur}[$, donc que le tableau est trié.

Proposition

L'algorithme est_trie est une solution au problème "est trié ?"

Complexité

Ligne à ligne :

  1. définition de la fonction $\mathcal{O}(1)$
  2. une boucle for de $K$ itérations
  3. un tests de deux valeurs dans un tableau : $\mathcal{O}(1)$
  4. un retour de fonction $\mathcal{O}(1)$
  5. un retour de fonction $\mathcal{O}(1)$

Que l'on sorte par le retour de la ligne 5 ou 6, le complexité est : $\mathcal{O}(k)$.

Cas le pire

Dans le cas le pire, on parcourt tout le tableau, donc :

Proposition

La complexité de l'algorithme est_trie est $\mathcal{O}(n)$ avec $n$ la taille du tableau en entrée.

Cas les meilleur

Dans le cas le meilleur, on s'arrête dès la première itération :

Proposition

La complexité minimale de l'algorithme est_trie est $\mathcal{O}(1)$ avec $n$ la taille du tableau en entrée.

Complexité en moyenne

La question est délicate. Il faut se demander quel est le modèle sous-jacent à notre tableau de nombres. Si on a aucune information sur la répartition des nombres, on a coutume d'utiliser le modèle suivant :

Modèle du tableau aléatoire

Définition

Un tableau $T$ d'entiers de longueur $n$ est un tableau aléatoire s'il résulte de la procédure suivante :

  • on tire une permutation $\sigma$ de $[0 \, ..\, n-1]$ de façon équiprobable (la probabilité de choisir $\sigma$ est $\frac{1}{n!}$)
  • $T[i] = \sigma(i)$ pour tout $[0 \, ..\, n-1]$

Dans un tableau aléatoire, toutes les valeurs sont différentes.

On utilise ce modèle par ce qu'il est simple à mettre en œuvre et à manipuler, tout en possédant de nombreuses propriétés :

Proposition

Si $T$ est un tableau aléatoire, la probabilité que $T[i] = k$ vaut :

$$ {\Pr}[T[i] = k] = \frac{1}{n} $$

preuve

Parmi les $n!$ permutations de $[0 \, ..\, n-1]$ il y en a $(n-1)!$ telles que $\sigma(i) = k$ (on fixe une valeur parmi $n$, il en reste $n-1$ qui font ce qu'elles veulent). La probabilité d'obtenir une telle permutation est alors $\frac{(n-1)!}{n!} = \frac{1}{n}$.

Comme les valeurs d'un tableau aléatoires sont deux à deux différentes, la proposition précédente peut s'interpréter aussi comme :

Corollaire

La probabilité que $T[i]$ soit le $k$ plus petit élément d'un tableau aléatoire de longueur $n$ vaut $\frac{1}{n}$, quelque soit $k$.

Enfin, dernière propriété qui va être utile dans les calculs :

Corollaire

Si $T$ est un tableau aléatoire, on a :

$$ {\Pr}[T[i] > k] = \frac{n-1-k}{n} $$

preuve

$$ \begin{array}{lcl} {\Pr}[T[i] > k] &=& \sum\limits_{u > k}{\Pr}[[T[i] = u]\\ &=& \sum\limits_{u > k}\frac{1}{n}\\ &=& \frac{n-1-k}{n}\\ \end{array} $$

Ce modèle véhicule de nombreuse propriété l'on aimerait avoir pour un tableau de nombres quelconques :

Montrez que si $T$ est un tableau aléatoire on a pour tout $u \neq v$ :

$$ {\Pr}[T[u] > T[v]] = {\Pr}[T[u] < T[v]] = \frac{1}{2} $$

corrigé

Puisque les valeurs de $T$ sont deux à deux différentes on a : ${\Pr}[T[u] > T[v]] + {\Pr}[T[u] < T[v]] = 1$ et comme il y a autant de tableaux pour lesquels $T[u] > T[v]$ que de tableaux pour lesquels $T[u] < T[v]$ (échanger les valeurs de $T[u]$ et $T[v]$ est une bijection entre les 2 ensembles de tableaux) on a ${\Pr}[T[u] > T[v]] = {\Pr}[T[u] < T[v]]$.

Des deux conditions précédentes on en déduit ${\Pr}[T[u] > T[v]] = {\Pr}[T[u] < T[v]] = \frac{1}{2}$.

Calcul de la complexité

Les propriétés précédentes nous permettent de voir que si $T$ est un tableau aléatoire alors la probabilité pour notre algorithme de reconnaissance fasse exactement :

Les évènements ne sont pas indépendant donc pour $p_2$ on a que :

$$ p_2 = {\Pr}[(T[0] < T[1]) \text{ et } (T[1] > T[2])] \neq {\Pr}[T[0] < T[1]] \;\cdot\; {\Pr}[T[1] > T[2]] $$

Mais comme :

$$ {\Pr}[(T[0] < T[1]) \text{ et } (T[1] > T[2])] = {\Pr}[(T[0] < T[1])] \;\cdot\; {\Pr}[T[1] > T[2] \;\mid\; T[1] > T[0]] $$

et que :

$$ {\Pr}[T[1] > T[2] \;\mid\; T[1] > T[0]] \leq {\Pr}[T[1] > T[2]] $$

puisque la partie gauche est plus contrainte pour $T[2]$ que la partie de droite (on a la valeur de $T[0]$ qui serait favorable en moins). On en conclut que :

$$ p_2 \leq {\Pr}[T[0] < T[1]] \;\cdot\; {\Pr}[T[1] > T[2]] = \frac{1}{2} \;\cdot\; \frac{1}{2} = \frac{1}{4} $$

Un raisonnement identique nous permet de minorer $p_i$ :

$$ p_i \leq {\Pr}[T[0] < T[1]] \cdot {\Pr}[T[1] < T[2]]\cdot \;\dots\; \cdot {\Pr}[T[i-2] < T[i-1]]\cdot {\Pr}[ T[i-i] > T[i]] = \frac{1}{2^i} $$

Comme chaque itération est de complexité $\mathcal{O}(1)$, la complexité en moyenne sous ce modèle de probabilité vaut :

$$ \begin{array}{lcl} C_{\text{moy}}(n) &=& \sum\limits_{i=1}^{n-1}(p_i \cdot (i \cdot \mathcal{O}(1)))\\ &\leq& \sum\limits_{i=1}^{n-1}(\mathcal{O}(\frac{i}{2^i})) \\ &\leq& \mathcal{O}(\sum\limits_{i=1}^{n-1}(\frac{i}{2^i})) \end{array} $$

Or la série des $\sum_{i=1}^{n}(\frac{i}{2^i})$ est toujours plus petite que 2 (on le démontrera) donc :

$$ C_\text{moy}(n) \leq \mathcal{O}(\sum\limits_{i=1}^{n}(\frac{i}{2^i})) \leq \mathcal{O}(2) = \mathcal{O}(1) $$

On a donc la proposition surprenante suivante :

Proposition

La complexité en moyenne du problème de la reconnaissance est en temps constant.

Ce résultat est remarquable :

À retenir

  • Pour le problème de la reconnaissance : la complexité en moyenne est égale à la complexité minimale et est en temps constant !
  • Ce n'est pas parce que la complexité augmente qu'elle en devient forcément infinie.
  • Borner une complexité par une série convergente est très utile pour démontrer qu'un algorithme est en temps constant.

Notez que l'on a utilisé un majorant des $p_i$ pour que le calcul soit aisé, mais on peut très bien calculer la valeur exacte de $p_i$ en comptant le nombre de tableaux où ces conditions sont vérifiées. Ce sont exactement les tableaux où :

Pour des tableaux de longueur $i$ il n'y a que $i$ tableaux possibles sur les $(i+1)!$ possibles pour une probabilité de $\frac{i}{(i+1)!}$. Le raisonnement est identique si on ne considère que les $i+1$ premiers éléments de tout tableau et donc $p_i = \frac{i}{(i+1)!}$ pour tout tableau de longueur $n\geq i+1$. On obtient alors :

$$ \begin{array}{lcl} C_\text{moy}(n) &=& \sum\limits_{i=1}^{n-1}(p_i \cdot (i \cdot \mathcal{O}(1)))\\ &=& \sum\limits_{i=1}^{n-1}(\mathcal{O}(\frac{i^2}{(i+1)!} \cdot i)) \\ &=& \mathcal{O}(\sum\limits_{i=1}^{n-1}(\frac{i^2}{(i+1)!})) \end{array} $$

Comme $i^2 \leq i(i+1)$ on a que :

$$ C_\text{moy}(n) \leq \mathcal{O}(\sum\limits_{i=1}^{n-1}(\frac{1}{(i-1)!})) = \mathcal{O}(\sum\limits_{i=0}^{n-2}(\frac{1}{i!})) $$

La série $\sum_{i=0}^{n-2}(\frac{1}{(i)!})$ converge vers $e$ ($e^x = \sum_{i=0}^{+\infty}(\frac{x^i}{i!})$ pour tout réel $x$) donc comme précédemment $C_\text{moy}(n) = \mathcal{O}(1)$

Vérification expérimentale

Je vois bien dans votre regard que vous ne me croyez pas. Essayez donc par vous même :

Testez le code suivant (chez moi le max était de 4) pour voir que le nombre maximum d'itération est très inférieur à la longueur du tableau (chez moi le max était de 4).

Puis essayez avec des tableaux plus contraints (comme T = 5 * list(range(4)) par exemple) pour voir que c'est toujours vrai si le tableau possède quelques égalités.

from random import shuffle

def compte(T):
    for i in range(len(T)-1):
        if T[i] > T[i+1]:
            return i +1
    return len(T) - 1


T = list(range(20))


nb = []
for i in range(100):
    shuffle(T)
    nb.append(compte(T))

print(nb)
print(max(nb))

Complexité du problème de la reconnaissance

Comme toute case du tableau peut rendre le tableau non trié, on utilise l'argument de la complexité du problème de la "recherche", un algorithme résolvant ce problème doit considérer toutes les cases du tableau et donc une borne min du problème "est trié ?" est $\Omega(n)$ où $n$ est la taille du tableau en entrée. Comme la complexité de est_trie est de $\mathcal{O}(n)$. On en conclut :

Proposition

La complexité du problème "est trié ?" est de $\Theta(n)$ où $n$ est la taille du tableau en entrée.