Tableaux
- Un tableau est une structure de données dans laquelle les éléments, tous de même type, occupent des positions contiguës en mémoire.
- Le nombre d’éléments qu’un tableau peut contenir est déterminé à la création d’un tableau.
Type Python | Type | Opération | Exemple | Complexité |
---|---|---|---|---|
N’existe pas | Tableau | Accès à un élément | tab[i] |
$O(1)$ |
Modification d’un élément | tab[i] = x |
$O(1)$ | ||
Effacement d’un élément | retire(tab, i) |
$O(n)$ | ||
Insertion d’un élément | insere(tab, x, i) |
$O(n)$ | ||
Recherche d’un élément | est_dans(tab, x) |
$O(n)$ |
- La structure de données appelée « liste » dans le langage Python est implémentée à l’aide de tableaux dynamiques.
Remarque : Dans la suite de ce document, on va considérer que la liste Python tab
, créé par l’instruction tab = [i for i in range(20)]
est de longueur fixe. Elle se comporte alors comme un tableau.
Pourquoi la recherche d’un élément dans un tableau quelconque (non trié donc) est en $O(N)$ ?
- Écrire le prédicat
est_dans
de spécification :
|
|
-
Écrire un jeu de tests.
-
(Facultatif) Écrire le prédicat
est_dans_rec
de spécification :
|
|
- Quelle est la complexité des algorithmes précédents en fonction de $N$, la taille du tableau ?
Réponse
- Dans l’algorithme itératif, dans le pire des cas (valeur absente du tableau) $N$ tours de boucle sont effectués.
- Dans l’algorithme récursif, $N$ appels récursifs sont effectués.
L’algorithme est donc en $O(N)$.
Pourquoi l’insertion d’un élément dans un tableau est en $O(N)$ ?
Dans cette partie, on imagine que le tableau a cette allure :
1 | 1 | 2 | 3 | 4 | 5 | vide | vide | vide |
---|
Si on introduit, en position 1, la valeur 7. Le tableau est alors :
7 | 1 | 1 | 2 | 3 | 4 | 5 | vide | vide |
---|
Remarque : Dans le code ci-dessous, les cellules seront considérées vides si elles contiennent la valeur None
.
- Écrire la fonction
insere
dont la spécification est :
|
|
- (Facultatif) Écrire la fonction
insere_rec
dont la spécification est :
|
|
- Quelle est la complexité des algorithmes précédents en fonction de $N$, la taille du tableau ?
Réponse
- Dans le pire des cas (insertion à la première place du tableau), on déplace $N-1$ valeurs.
L’algorithme est donc en $O(N)$.
Structure de liste chaînée
Les listes chaînées constituent une structure de données :
- de longueur modifiable ;
- plus efficace que les tableaux lorsqu’il s’agit d’ajouter ou de retirer un élément (il n’est pas nécessaire de faire de la place en déplaçant les éléments) ;
- qui servira de brique à l’élaboration d’autres structures de données.
Une liste chaînée permet de représenter une liste ; chaque élément de cette liste est une cellule contenant :
- la valeur de l’élément à stocker ;
- l’adresse mémoire de la cellule représentant l’élément suivant.
Remarque : le symbole $\perp$ marque la fin de la liste dans les schémas.
Comment implémenter une cellule ?
En langage Python, on peut implémenter une cellule à l’aide d’une classe, de listes ou de tuples.
- Définir la classe
Cellule
dont la spécification est :
|
|
None
qui représente donc une cellule vide.
-
Instancier 3 objets
c1
,c2
,c3
, de typeCellule
, dont les valeurs sont 1, 2, 3. Tous ces objets sont pour l’instant isolés (ils ne pointent vers aucune autre cellule si ce n’est la cellule videNone
). -
Créer la liste
lst
à l’aide de ces trois objets. -
Comment aurait-on pu créer la liste
lst
en une seule instruction ?
Les listes chaînées sont des structures fondamentalement récursives
Une liste chaînée est :
- soit la liste vide (objet
None
) ; - soit constituée de son premier élément (objet de type
Cellule
) et du reste des éléments qui forment aussi une liste. Une liste chaînée est donc une structure récursive.
Une liste chaînée est-elle forcément homogène
Tout au long de ce document nous allons uniquement envisager des listes chaînées de nombres entiers. Rien n’impose, cependant, à l’attribut valeur
de la classe Cellule
d’être un entier. Chaque cellule peut même posséder des attributs valeur
de type différent. Les listes chaînées peuvent donc être hétérogènes.
Quelques opérations sur les listes chaînées
Nous créerons une classe enveloppe dans un prochain document, de façon à ce notre liste chaînée ait la même interface qu’une liste Python.
Longueur d’une liste chaînée
- Écrire la fonction
longueur
dont la spécification est :
|
|
- Écrire la fonction
longueur_iter
dont la spécification est :
|
|
- Déterminer la complexité du calcul de la longueur d’une liste.
Réponse
On réalise autant d’appels récursifs (ou de tours de boucles qu’il y a d’éléments dans la liste).
La complexité est donc en $O(N)$.
Affichage de tous les éléments d’une liste
- Écrire la fonction
affichage_elements_liste
dont la spécification est :
|
|
- Définir la fonction
list_to_str
dont la spécification est :
|
|
Valeur du n-ième élément d’une liste
- Écrire la fonction
n_ieme_element
dont la spécification est :
|
|
- Écrire la fonction
n_ieme_element_iter
dont la spécification est :
|
|
- Déterminer la complexité de la recherche du n-ième élément d’une liste.
Réponse
- Si l’élément dont on cherche la valeur est le dernier de la liste il est nécessaire de faire $N$ appels récursifs (ou tours de boucles).
- Si l’indice est hors des limites, il est ici aussi nécessaire de parcourir toute la liste avant de s’en rendre compte. L’algorithm est donc en $O(N)$.
Modification de la valeur d’une cellule sans modifier la structure de la liste
- Écrire la fonction
modifier_n_ieme_element
dont la spécification est :
|
|
Ajout d’une valeur à la fin de la liste
- Écrire la fonction
ajout_fin_liste
dont la spécification est :
|
|
- Quelle est la complexité de la fonction
ajout_fin_liste
?
Réponse
On doit parcourir toute la liste pour ajouter un élément à la fin. La complexité est donc linéaire.
Ajout d’une valeur au début de la liste
- Écrire la fonction
ajout_debut_liste
dont la spécification est :
|
|
- Quelle est la complexité de cette fonction ?
Réponse
$O(1)$
Retrait du dernier élément d’une liste
- Écrire la fonction
retrait_dernier_element
dont la spécification est :
|
|
- Quelle est la complexité de cette fonction ?
Réponse
Il faut parcourir la liste, donc $O(N)$.
Retrait du premier élément d’une liste
- Écrire la fonction
retrait_premier_element
dont la spécification est :
|
|
- Quelle est la complexité de cette fonction ?
Réponse
$O(1)$.
Retrait du n-ième élément d’une liste
- Écrire la fonction
retrait_n_ieme_element
dont la spécification est :
|
|
- Quelle est la complexité de cette fonction ?
Réponse
Le retrait en lui-même est en $O(1)$ mais il faut arriver jusqu’à la valeur. L’ensemble est donc en $O(N)$.
Concaténation de deux listes
- Écrire la fonction
concatener
dont la spécification est :
|
|
- Écrire la fonction
copie
dont la spécification est :
|
|
- Se servir de la fonction
copie
pour écrire la fonctionconcatener_avec_copie_integrale
dont la spécification est :
|
|
- Quelle est la complexité de la fonction
concatener
? Même question pour la fonctionconcatener_avec_copie_integrale
.
Réponse
- Dans le premier cas, il est nécessaire de recopier tous les éléments de
lst1
. La complexité est donc linéaire et dépend du nombre d’élements delst1
. - Dans le second cas, on recopie tous les éléments de
lst1
et delst2
. La complexité est donc linéaire et est la somme du nombre d’éléments delst1
et delst2
.
Renverser une liste
- Écrire la fonction
renverser
dont la spécification est :
|
|
Remarque. Cette fonction doit s’appuyer sur les fonctions concatener
ou concatener_avec_copie_integrale
.
Remarque. Cet algorithme est particulièrement inefficace.
Conclusion
- Une liste chaînée est une structure de données utile pour représenter une séquence finie d’éléments.
- Chaque élément est contenu dans une cellule, qui fournit, en plus de la valeur, un moyen pour accéder à la cellule suivante.
- Les éléments d’une liste chaînée ne sont pas situés dans des emplacements contigüs en mémoire.
- Les opérations sur les listes chaînées se programment sous la forme de parcours qui suivent ces liaisons, en utilisant la récursivité ou des boucles.
Une liste chaînée est un type abstrait de données qui doit, au minimum, fournir les fonctions suivantes :
insère
: insère un élément dans la liste ;supprime
: supprime et renvoie l’élément de position spécifié de la liste ;longueur
: renvoie le nombre d’éléments de la liste ;est_vide
: détermine si la liste est vide ou pas ;nième_élément
: retourne la valeur du nième élément depuis le début.
Action | Liste chaînée | Tableau | Tableau dynamique |
---|---|---|---|
Accès à un élément | $O(n)$ | $O(1)$ | $O(1)$ |
Insertion/suppression du 1er élément | $O(1)$ | $O(n)$ (si place) | $O(n)$ |
Insertion à la fin | $O(n)$ | $O(1)$ (si place) | $O(1)$ (si place) $O(n)$ (si pas de place) |
Suppression du dernier élément | $O(n)$ | $O(1)$ | $O(1)$ |
Insertion/suppression au milieu | $O(n)$ | $O(n)$ (si place) | $O(n)$ |
Quelques exemples d’utilisation des listes chaînées
-
Les logiciels de visualisation d’images utilisent parfois une liste chaînée pour afficher les images précédentes et suivantes à l’aide des boutons « précédent » et « suivant ».
-
Des navigateurs Web peuvent utiliser une liste chaînée pour accéder aux pages à l’aide des boutons « précédent » et « suivant ».
-
Les boutons « précédent » et « suivant » des lecteurs de musique peuvent utiliser une liste chaînée doublement/circulaire.
-
Les tracés et les formes sur le canevas, de MS-Paint sont connectés via une liste chaînée.
-
Le balayage « gauche/droite » sur Tinder utilise une liste doublement chaînée.
-
On peut garder la trace des tours dans un jeu multi-joueurs à l’aide d’une liste chaînée circulaire.
-
Les lignes de code dans un IDE peuvent être enregistrées dans une liste doublement chaînée.
Annexe : Tests possibles pour les fonctions codées
|
|