Ma compréhension...
Avantages:
Désavantage:
En regardant ces avantages et ces inconvénients, je ne vois pas pourquoi une liste liée éviterait jamais d'utiliser un pointeur de queue. Y a-t-il quelque chose qui me manque?
Vous êtes correct, un pointeur de queue ne fait jamais mal et ne peut que vous aider. Cependant, il y a une situation où l'on n'a pas besoin d'un pointeur de queue du tout.
Si l'on utilise une liste liée pour implémenter une pile, il n'y a pas besoin d'un pointeur de queue, car on peut garantir que tous les accès, insertions et déménagements se produisent à la tête. Cela étant dit que l'on pourrait utiliser une liste doublement liée avec un pointeur de queue de toute façon car c'est la mise en œuvre standard dans une bibliothèque ou une plate-forme et une mémoire est bon marché, mais elle n'est pas besoin.
Les listes liées sont très communément persistantes et immuables. En fait, dans les langages de programmation fonctionnelle, cette utilisation est omniprésente. Les pointeurs de queue rompent les deux propriétés. Cependant, si vous ne vous souciez pas de l'immuabilité ni de la persistance, il y a très peu d'inconvénients pour inclure un pointeur de queue.
J'utilise rarement un pointeur de queue pour des listes liées et j'ai tendance à utiliser des listes individuellement liées plus souvent, où une structure d'insertion et de suppression de la pépinette de pile (ou d'élimination de temps linéaire du milieu) suffit. C'est parce que dans mes cas d'utilisation courants, le pointeur de la queue est effectivement coûteux, tout comme pour rendre la liste individuellement liée à une liste doublement liée.
Souvent, mon utilisation en cas de cas commun pour une liste individuelle peut stocker des centaines de milliers de listes liées qui ne contiennent que quelques nœuds de liste chacune. Je n'utilise pas non plus généralement à des pointeurs pour des listes liées. J'utilise des indices dans un tableau à la place car les indices peuvent être 32 bits, par exemple, prenant la moitié de l'espace d'un pointeur 64 bits. Je n'alloue pas non plus généralement aux nœuds de liste une à la fois et, à nouveau, utilisez simplement un grand tableau pour stocker tous les nœuds, puis utilisez des indices 32 bits pour relier les nœuds ensemble.
À titre d'exemple, imaginez un jeu vidéo utilisant une grille de 400x400 pour partitionner un million de particules qui se déplacent et se rebondissent les unes des autres pour accélérer la détection de collision. Dans ce cas, une façon assez efficace de stocker consiste à stocker 160 000 listes locales, qui se traduisent par 160 000 entiers 32 bits dans mon cas (~ 640 kilobytes) et une au-tête entier 32 bits par particules. Maintenant, lorsque les particules se déplacent sur l'écran, tout ce que nous avons à faire est de mettre à jour quelques entiers 32 bits pour déplacer une particule d'une cellule à l'autre, comme si:
... avec l'index next
("pointeur") d'un nœud de particules servant d'index sur la particule suivante dans la cellule ou la particule libre suivante à récupérer si la particule est morte (essentiellement une liste gratuite Implémentation d'allocator à l'aide des indices):
L'élimination de temps linéaire d'une cellule n'est pas en réalité une surcharge puisque nous traitons la logique de particules en itérant à travers les particules dans une cellule, une liste doublement doublement liée ajouterait simplement des frais généraux d'une sorte qui n'est pas bénéfique à Tout dans mon cas, tout comme une queue ne me profiterait pas du tout.
Un pointeur de queue double l'utilisation de la mémoire de la grille ainsi que l'augmentation du nombre de caches manquées. Il nécessite également une insertion nécessaire à une branche pour vérifier si la liste est vide au lieu d'être sans succursale. En ce qui concerne une liste doublement liée doublerait la surcharge de la liste de chaque particule. 90% du temps que j'utilise des listes liées, c'est pour des cas comme ceux-ci, et un pointeur de queue serait donc relativement assez coûteux à stocker.
Donc, 4 à 8 octets ne sont en réalité pas triviaux dans la plupart des contextes dans lesquels j'utilise des listes liées en premier lieu. Je voulais juste puce là-bas car si vous utilisez une structure de données pour stocker une charge de bateau d'éléments, 4 à 8 octets peuvent ne pas toujours être si négligeables. J'utilise effectivement des listes liées à Réduire Les allocations de mémoire numérique et la quantité de mémoire requises par opposition à, disent, stockant 160 000 matrices dynamiques qui poussent pour la grille qui aurait une utilisation de la mémoire explosive (typiquement un pointeur plus deux entiers au moins par cellule de grille ainsi que des allocations de tas par cellule de grille par opposition à un seul entier et à zéro allocations par cellule).
Je trouve souvent beaucoup de personnes atteintes de listes liées pour leur complexité de temps constante associée à l'élimination avant/intermédiaire et à l'insertion avant/moyenne, lorsque LLS est souvent un choix médiocre dans ces cas en raison de leur manque général de contiguïté. Où les LLS sont belles à partir d'un point de vue de la performance, c'est la possibilité de simplement déplacer un élément d'une liste à une autre en manipulant simplement quelques pointes et de réaliser une structure de données de taille variable sans allocateur de mémoire de taille variable (puisque Chaque nœud a une taille uniforme, nous pouvons utiliser des listes gratuites, par exemple). Si chaque nœud de liste est alloué individuellement sur un allocator à usage général, il s'agit généralement du moment où les listes liées sont beaucoup moins pires que les alternatives, et c'est souvent par une utilisation erronée dans ces contextes où ils finissent par avoir une mauvaise réputation dans des langues comme C++.
Je suggérerais plutôt que pour la plupart des cas où des listes liées servent d'optimisation très efficace sur des solutions de rechange simples, les formes les plus utiles sont généralement liées seuls, n'ont besoin que d'un pointeur de tête et ne nécessitent pas d'allocation de mémoire à usage général par Nœud et peuvent plutôt souvent, la mémoire de la piscine déjà allouée par nœud (à partir d'un grand tableau déjà alloué à l'avance, par exemple). De plus, chaque SLL stockerait généralement un très petit nombre d'éléments dans ces cas, tels que les bords connectés à un nœud graphique (de nombreuses minuscules listes liées par opposition à une liste liée massive).
Il convient également de garder à l'esprit que nous avons une charge de bateau de DRAM ces jours-ci, mais c'est le deuxième type de mémoire la plus lente disponible. Nous sommes toujours à quelque chose comme 64 Ko par noyau lorsqu'il s'agit du cache L1 avec des lignes de cache de 64 octets. En conséquence, ces économies d'octets peu d'octets peuvent vraiment compter dans une zone critique de performance, comme la SIM de particules ci-dessus lorsqu'il est multiplié des millions de fois si cela signifie la différence entre stocker deux fois plus de nœuds dans une ligne de cache ou non, par exemple.