J'ai essayé "heapq" et suis arrivé à la conclusion que mes attentes différaient de ce que je voyais à l'écran. J'ai besoin de quelqu'un pour expliquer comment cela fonctionne et où cela peut être utile.
Extrait du livre Module Python de la semaine au paragraphe 2.2 Tri il est écrit
Si vous devez conserver une liste triée à mesure que vous ajoutez et supprimez des valeurs, consultez heapq. En utilisant les fonctions de heapq pour ajouter ou supprimer des éléments d’une liste, vous pouvez conserver l’ordre de tri de la liste avec un temps système minimal.
Voici ce que je fais et reçois.
import heapq
heap = []
for i in range(10):
heap.append(i)
heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
heapq.heapify(heap)
heapq.heappush(heap, 10)
heap
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
heapq.heappop(heap)
0
heap
[1, 3, 2, 7, 4, 5, 6, 10, 8, 9] <<< Why the list does not remain sorted?
heapq.heappushpop(heap, 11)
1
heap
[2, 3, 5, 7, 4, 11, 6, 10, 8, 9] <<< Why is 11 put between 4 and 6?
Ainsi, comme vous le voyez, la liste "tas" n'est pas du tout triée. En fait, plus vous ajoutez et supprimez des éléments, plus elle devient encombrée. Les valeurs poussées prennent des positions inexplicables. Que se passe-t-il?
Le module heapq
gère l'invariant de tas , ce qui n'est pas la même chose que le maintien de l'objet de liste dans un ordre trié.
Citant la heapq
documentation :
Les tas sont des arbres binaires pour lesquels chaque nœud parent a une valeur inférieure ou égale à l'un de ses enfants. Cette implémentation utilise des tableaux pour lesquels
heap[k] <= heap[2*k+1]
Etheap[k] <= heap[2*k+2]
Pour toutk
, en comptant les éléments à partir de zéro. À des fins de comparaison, les éléments non existants sont considérés comme infinis. La propriété intéressante d'un tas est que son plus petit élément est toujours la racine,heap[0]
.
Cela signifie qu'il est très efficace de trouver le plus petit élément (il suffit de prendre heap[0]
), Ce qui est excellent pour une file d'attente prioritaire. Après cela, les 2 valeurs suivantes seront plus grandes (ou égales) que la 1ère, et les 4 suivantes seront plus grandes que leur nœud "parent", les 8 suivantes seront plus grandes, etc.
Vous pouvez en savoir plus sur la théorie derrière la structure de données dans la section Section Théorie de la documentation . Vous pouvez également regarder ce cours du MIT cours OpenCourseWare Introduction à l'algorithme) , qui explique l'algorithme en termes généraux.
Un tas peut être reconverti très efficacement en une liste triée:
def heapsort(heap):
return [heapq.heappop(heap) for _ in range(len(heap))]
en sautant simplement l'élément suivant du tas. Cependant, utiliser sorted(heap)
devrait être encore plus rapide, car l’algorithme TimSort utilisé par le tri de Python tirera parti du classement partiel déjà présent dans un segment de mémoire.
Vous utiliseriez un tas si vous êtes uniquement intéressé par la plus petite valeur, ou par la première n
plus petite valeur, en particulier si vous êtes intéressé par ces valeurs de manière continue; ajouter de nouveaux éléments et supprimer les plus petits est très efficace, bien plus que de recourir à la liste à chaque fois que vous ajoutez une valeur.
Votre livre est faux! Comme vous le démontrez, un segment de mémoire n'est pas une liste triée (bien qu'une liste triée soit un segment de mémoire). Qu'est-ce qu'un tas? Pour citer le manuel de conception d'algorithmes de Skiena
Les tas sont une structure de données simple et élégante pour prendre en charge efficacement les opérations de file d'attente prioritaires, insert et extract-min. Ils fonctionnent en maintenant un ordre partiel sur l'ensemble des éléments qui est plus faible que l'ordre trié (il peut donc être efficace de le maintenir) mais plus fort que l'ordre aléatoire (afin que l'élément minimum puisse être rapidement identifié).
Comparé à une liste triée, un segment obéit à une condition plus faible l'invariant du segment. Avant de la définir, demandez-vous pourquoi il pourrait être utile de détendre la condition. La réponse est la condition la plus faible est plus facile à maintenir. Vous pouvez faire moins avec un tas, mais vous pouvez le faire plus vite.
Un tas a trois opérations:
L'insertion cruciale est O (log n) qui bat O(n) pour une liste triée.
Quel est l'invariant de tas? "Un arbre binaire où les parents dominent leurs enfants". C'est, "p ≤ c
_ pour tous les enfants c de p ". Skiena illustre avec des images et montre ensuite l’algorithme pour insérer des éléments tout en maintenant l’invariant. Si vous réfléchissez un moment, vous pouvez les inventer vous-même. (Indice: ils sont connus comme des bulles et bouillonner)
La bonne nouvelle est que les piles incluses Python implémente tout pour vous, dans le module heapq . Cela ne définit pas un type de tas (ce qui, à mon avis, serait plus facile) à utiliser), mais les fournit comme fonctions d’aide sur la liste.
Moral: Si vous écrivez un algorithme à l'aide d'une liste triée mais que vous n'inspectez et ne supprimez qu'une extrémité, vous pouvez le rendre plus efficace en utilisant un segment de mémoire.
Pour un problème dans lequel une structure de données de tas est utile, lisez https://projecteuler.net/problem=5
Il existe un certain malentendu quant à la mise en œuvre de la structure de données en tas. Le module heapq
est en fait une variante de l'implémentation de tas binaire , où les éléments de tas sont stockés dans une liste, comme décrit ici: https: //en.wikipedia. org/wiki/Binary_heap # Heap_implementation
Citant Wikipedia:
Les tas sont généralement implémentés avec un tableau. Tout arbre binaire peut être stocké dans un tableau, mais comme un tas binaire est toujours un arbre binaire complet, il peut être stocké de manière compacte. Aucun espace n'est requis pour les pointeurs. au lieu de cela, le parent et les enfants de chaque nœud peuvent être trouvés par arithmétique sur des index de tableau.
Cette image ci-dessous devrait vous aider à ressentir la différence entre la représentation arborescente et la liste du tas et ( remarque, qu'il s'agit d'un tas max, qui est l'inverse de l'habituel min-tas! ):
En général, la structure de données en tas est différente d'une liste triée en ce sens qu'elle sacrifie certaines informations sur le fait qu'un élément particulier est plus grand ou plus petit qu'un autre. Heap seul peut dire que cet élément particulier est moins important que parent et plus grand que ses enfants. Moins une structure de données stocke d'informations, moins de temps/mémoire est nécessaire pour la modifier. Comparez la complexité de certaines opérations entre un tas et un tableau trié:
Heap Sorted array
Average Worst case Average Worst case
Space O(n) O(n) O(n) O(n)
Search O(n) O(n) O(log n) O(log n)
Insert O(1) O(log n) O(n) O(n)
Delete O(log n) O(log n) O(n) O(n)