• Aucun résultat trouvé

Implémentation des listes

N/A
N/A
Protected

Academic year: 2022

Partager "Implémentation des listes"

Copied!
3
0
0

Texte intégral

(1)

Implémentation des listes

Lycée Berthollet, MP/MP* 2021-22

I Présentation du problème

Ce court texte a pour objet de discuter de l’implémentation des objets Python de typelist et des conséquences qui en résultent du point de vue de la complexité des algorithmes.

Rappelons tout d’abord ce qu’est un objet de type list: un tel objet L est un suite finie ordonnée d’objets Python de type quelconque (en particulier, ils peuvent eux aussi être de type list). S’il y anobjets, ils sont indexés par les entiersi∈[[0,n−1]]et on peut accéder directe- ment à l’élément de la liste d’indicei, soit pour le consulter, soit pour le modifier, par la syntaxe L[i](notation usuelle en informatique). L’intérêt de ce typelistest qu’il permet facilement de rajouter des éléments à la fin de la liste (la taille de la liste n’est donc pas fixée une fois pour toute) voire de supprimer ou insérer des éléments ou même des sous-listes. On parle dans ces cas-là de gestiondynamiquede la mémoire.

Il y a classiquement deux manières d’implémenter de telles listes : à l’aide detableaux dy- namiquesou à l’aide de listes chaînées. Il semble que le type d’implémentation ne soit pas imposé par la spécification du langage Python.Cela a comme conséquence que la complexité des opérations sur les listes peut dépendre de l’implémentation utilisée. Remarquons que l’im- plémentation aujourd’hui majoritairement utilisée estCPython(c’est le cas par exemple si vous travaillez souspyzo) et que celle-ci utilise les tableaux dynamiques. Il est cependant intéressant de comprendre aussi l’implémentation par listes chaînées, car selon la manière dont vous vou- lez utiliser vos “listes”, il pourrait être préférable de créer votre propre type pour des raisons de complexité. Il est à noter que, mis à part les problèmes de complexité, chacune de ces deux méthodes permet de comprendre par des représentations à l’aide de pointeurs (représentés par des flèches) les subtilités des objets Python de typelistet en particulier le fait que les listes sont des objetsmuables.

Notons enfin qu’on pourrait éventuellement imaginer d’autres implémentations plus sophis- tiquées de ces objets qui respecteraient la définition du langage Python.

II Tableaux dynamiques

On appellera icicaseun certain nombre (fixé suivant la machine) d’octets consécutifs de la mémoire permettant de stocker au total une adresse mémoire et elle-même repérée par un entier qui est son adresse mémoire. Un tableau dynamique de taillen va être représenté en mémoire par la donnée de l’entiern(nous ne parlerons pas ici de son stockage) et dencases consécutives, chacune contenant l’adresse d’un objet Python (i.e.unpointeurvers un objet Python). Pour lire

1

(2)

l’élément d’indicei, il suffit donc d’aller chercher son adresse dans la(i+1)-ème case et pour cela d’ajouter i à l’adresse de début de tableau. Cela se fait en temps constant Θ(1). Pour modifier cet élément, il suffit de faire pointer la case correspondante sur un autre objet Python ce qui se fait aussi enΘ(1).

Pour ajouter un élément à la liste, il faut modifiern(ce dont nous ne parlerons pas) et ajouter une case au tableau. Si la case qui suit directement lesncases consécutives est “libre”, pas de problème, on y met le pointeur qui désigne l’élément ajouté, ce qui se fait enΘ(1). Si ce n’est pas le cas, on doit alors déplacer tout le tableau vers une zone libre plus grande, où on pourra le prolonger, ce qui nécessite la recopie intégrale desncases et se fait enΘ(n). Pour que cela arrive le moins souvent possible, on utilise d’habitude le procédé suivant : au départ, on prévoit en général plus de place que lesncases initiales, au cas où on voudrait prolonger la liste. Puis lors de chaque déplacement forcé, on s’arrange pour doubler la place mémoire disponible et la réserver. Cela réduit le nombre de déplacements et le coût d’ajout de p éléments est alors en Θ(max(n,p)). Par exemple, si on part d’une liste vide et on ajoute un à unnéléments à la liste, cela fait un nombre maximal de recopies de cases'n+n2+n4· · ·=Θ(n), et la complexité de l’opération globale a le même ordre de grandeur que s’il n’y avait pas eu de déplacement ! En pratique, onlissele coût des déplacements et on fait l’approximationque les ajouts se font en temps constant, convention à adopter dans vos calculs de complexité un jour d’épreuve.

Cependant, si on connaît au départ la taille de la liste, il est vivement conseillé de réserver dès le départ la zone mémoire nécessaire au stockage du tableau dynamique, par exemple à l’aide d’une instruction L = [0]*n. Remarquons au passage que la création d’une liste par compréhension (du type [0 for i in range(n)]) est implémentée de façon très efficace et peut remplacer la réservation précédente. Dans le cas d’une liste de liste, la “compréhension”

est même laméthode à utiliser, à cause de l’erreur fréquente obtenue avec le code fautifL = [[0]*n]*p(la modification d’une ligne impactant alors toutes les lignes !).

Pour enlever un élément à la liste, ou insérer un élément à la liste, il faut décaler d’une case tous les pointeurs à partir de l’endroit de suppression/insertion, ce qui est d’autant plus coûteux que l’élément supprimé est proche du début du tableau. Cela se fait au pire enΘ(n).

On peut voir plus de détails sur les complexités des opérations sur les listes en CPythonà l’adressehttps://wiki.python.org/moin/TimeComplexity.

III Listes chaînées

Une liste chaînée est une suite d’entités, stockées éventuellement à des endroits éloignés les uns des autres en mémoire, dont chacune :

— contient un pointeur vers un objet

— contient un pointeur vers l’entité suivante

La dernière entité admet comme “suivante” un objet spécifique, usuellement noté NIL, si- gnifiant la fin de la liste chaînée. La liste est alors donnée par un pointeur vers la première entité.

On peut au besoin créer facilement une telle classe en Python.

Dans le cas d’une implémentation par liste chaînée, l’accès au i-ème élément se fait en parcourant la liste, donc au pire enΘ(n), ce qui est nettement moins bon que dans le cas d’un tableau dynamique. En revanche, l’ajout se fait toujours en temps constant, et l’insertion ou la suppression sont beaucoup plus rapides quand on est proche du début de la liste.

2

(3)

IV Conclusion

Au niveau des CPGE et des concours qu’elles préparent, il semble raisonnable d’utiliser les complexités issues de l’implémentationCPythonpar tableaux dynamiques, en particulier :

— Lecture d’un élément :Θ(1)

— Modification d’un élément :Θ(1)

— Insertion/Suppression d’un élément :Θ(n)

et de faire l’approximationque l’ajout d’un élément en fin de liste se fait enΘ(1).

V Appendice : remarque sur le type ndarray

Les objets de typenumpy.ndarraysont eux des tableaux (éventuellement multidimension- nels) au sens “classique” du terme : il utilisent une gestionstatiquede la mémoire : lors de la création d’un tableau de n flottants, par exemple, si on note p le nombre de cases mémoires nécessitant le stockage d’un flottant (qui peut varier suivant la machine et l’implémentation), on réserve n×p cases consécutives et on stocke directement chaque flottant dans les p cases réservées à cet égard. L’accés et la modification se font donc en temps constantΘ(1). L’ajout, l’insertion et la suppression sur place ne sont pas possibles.

Rappelons par ailleurs une fois encore, même si ce n’est ici pas le sujet, que l’utilisation du slicingne crée pas pour ce type de recopie des données, contrairement aux listes. La modifica- tion de l’objet résultant duslicingmodifie ainsi le tableau initial.

3

Références

Documents relatifs

Quand on ne dispose que d'un langage très pauvre, sans types de données composées, la solution précédente n'est plus possible, mais il y a un palliatif : on crée deux

Returns the index in this list of the first occurrence of the specified element, or -1 if the List does not contain this element. Retourner l’indice de la première occurence

Fonctionnement d'une pile La structure de pile permet de retrouver les éléments dans l'ordre inverse de 1 Une structure de file permet de retrouver les éléments dans l'ordre où

b) Sur un tas binaire, écrivez l'algorithme d'un traitement récursif propMax(i) permettant d'assurer qu'à partir de l'indice i chaque élément du tableau est

• Les piles sont utilisées pour implanter les appels de procédures (cf. pile système). • En particulier, les procédures récursives gèrent une pile

Liste Cons(float tete, Liste queue) : Construit une nouvelle liste dont la valeur de la première cellule est tete et dont le suivant sera queue5. Il s’agit d’un insertion en tête

[r]

FICHIERS FICHIERS Mémoire de masse découpée en blocs Fichier : liste chaînée de blocs, ou.. arbre de blocs (répertoires