Stacks et Files d'attente basés sur les tableaux vs listes D'attente basées sur les tableaux

j'essaie de comparer les taux de croissance (temps d'exécution et espace) des opérations de pile et de file d'attente lorsqu'elles sont mises en œuvre sous forme de tableaux et de listes liées. Jusqu'à présent, je n'ai pu trouver que des temps d'exécution de cas moyens pour la file pop() s, mais rien qui explore complètement ces deux structures de données et compare leurs temps d'exécution/comportements de l'espace.

plus précisément, je cherche à comparer push() et pop() pour les files d'attente et les piles, mis en œuvre comme à la fois tableaux et listes liées (donc 2 opérations x 2 structures x 2 implémentations, ou 8 valeurs).

de plus, j'apprécierais des valeurs de meilleur, moyen et pire pour ces deux-là, et tout ce qui concerne la quantité d'espace qu'ils consomment.

la chose la plus proche que j'ai pu trouver est cette" mère de tous les cs cheat sheets " pdf qui est clairement une masters - ou doctoral-niveau cheat sheet d'algorithmes avancés et fonctions discrètes.

je cherche juste un moyen de déterminer quand et où je devrais utiliser une implémentation basée sur un tableau par rapport à une implémentation basée sur une liste pour les piles et les files d'attente.

37
demandé sur templatetypedef 2011-09-20 00:55:01

2 réponses

il y a plusieurs façons différentes d'implémenter des files d'attente et des piles avec des listes et des tableaux liés, et je ne suis pas sûr de celles que vous recherchez. Avant d'analyser n'importe laquelle de ces structures, cependant, passons en revue quelques considérations importantes de durée d'exécution pour les structures de données ci-dessus.

dans une liste mono-liée avec juste un pointeur de tête, le coût de préparer une valeur est O (1) - Nous créons simplement le nouvel élément, filez son pointeur pour pointer vers l'ancienne tête de la liste, puis mise à jour le pointeur de têtes. Le coût de suppression du premier élément est également O(1), ce qui est fait en mettant à jour le pointeur de tête pour pointer vers l'élément après la tête courante, puis en libérant la mémoire pour l'ancienne tête (si gestion explicite de la mémoire est effectuée). Toutefois, les facteurs constants dans ces termes de O(1) peuvent être élevés en raison du coût des allocations dynamiques. La mémoire supérieure de la liste liée est généralement O (n) mémoire supplémentaire totale en raison du stockage d'un pointeur supplémentaire dans chaque élément.

dans un tableau dynamique, nous pouvons accéder à n'importe quel élément dans le temps O(1). Nous pouvons également ajouter un élément dans amorti O(1) , ce qui signifie que le temps total pour n insertions est O(n), bien que les limites de temps réel sur toute insertion peuvent être bien pire. Généralement, les tableaux dynamiques sont mis en œuvre en ayant la plupart des insertions prennent O (1) en ajoutant dans l'espace préallocé, mais ayant un petit nombre d'insertions exécutées dans le temps Θ (n) en doublant la capacité du tableau et copie d'éléments. Il existe des techniques pour tenter de réduire cela en allouant de l'espace supplémentaire et en copiant paresseusement les éléments (voir cette structure de données , par exemple). Typiquement, l'utilisation de la mémoire d'un tableau dynamique est assez bonne - quand le tableau est complètement plein, par exemple, il n'y a que O(1) overhead supplémentaire - bien que juste après que le tableau a doublé de taille il peut y avoir O(n) éléments inutilisés alloués dans le tableau. Parce que les allocations sont peu fréquentes et les accès sont rapides, les tableaux dynamiques sont généralement plus rapides que les listes chaînées.

maintenant, réfléchissons à la façon d'implémenter une pile et une file d'attente en utilisant une liste liée ou un tableau dynamique. Il y a plusieurs façons de le faire, donc je suppose que vous utilisez les implémentations suivantes:

examinons chacun à tour de rôle.

pile supportée par une liste mono-liée. Parce qu'une liste avec un seul lien supporte O(1) time prepend et delete-first, le coût pour push ou pop dans une pile avec un lien est aussi O(1) worst-case. Toutefois, chaque nouvel élément ajouté nécessite une nouvelle affectation, et les affectations peuvent être coûteuses par rapport à d'autres opérations.

Pile soutenu par un tableau dynamique. en Poussant sur la pile peut être mis en œuvre par l'ajout d'un nouvel élément du tableau dynamique, qui prend amorti O(1) et le temps le pire des cas O(n) fois. La sortie de la pile peut être implémentée en enlevant simplement le dernier élément, qui tourne dans le pire des cas O(1) (ou amorti O(1) si vous voulez essayer de récupérer l'espace non utilisé). En d'autres termes, l'implémentation la plus commune a le meilleur-cas o(1) push et pop, le pire-cas O(n) push et O(1) pop, et amorti O(1) push et O(1) pop.

Queue supportée par une liste mono-liée. Les Enquêtes sur la liste liée peuvent être mises en œuvre par annexe à la fin de la liste à liens simples, ce qui prend le temps O(1) dans le pire des cas. Dequeuing peut être mis en œuvre en supprimant le premier élément, ce qui prend également le temps O(1) dans le pire des cas. Cela nécessite également une nouvelle allocation par demande, qui peut être lente.

Queue soutenue par un tampon circulaire croissant. se renseigner sur le tampon circulaire fonctionne en insérant quelque chose à la position libre suivante dans le tampon circulaire. Cela fonctionne en cultivant le tableau si nécessaire, puis insérer le nouvel élément. En utilisant une analyse similaire pour le tableau dynamique, cela prend le meilleur temps O(1), le pire temps O(n), et le temps amorti O(1). Dequeuing à partir du buffer fonctionne en enlevant le premier élément du buffer circulaire, ce qui prend le temps O(1) dans le pire des cas.

pour résumer, toutes les structures supportent les éléments poussant et sautant n en O(n) temps. Les versions de liste liées ont un meilleur comportement au pire, mais peut avoir un pire durée d'exécution globale en raison du nombre de répartitions effectuées. Les versions array sont plus lentes dans le pire des cas, mais ont de meilleures performances globales si le temps par opération n'est pas trop important.

une autre option que vous voudrez peut-être examiner pour mettre en œuvre stacks est le VList , une structure de données récente qui est un hybride d'une liste liée et d'un tableau dynamique. Elle fait moins de dotations qu'une liste liée et y a moins de points d'appui, bien que la l'utilisation de l'espace est un peu plus élevé dans le pire des cas. Vous pourriez également vouloir examiner les chunklists, qui sont un autre hybride de tableaux et de listes liées, qui peuvent être utilisés à la fois pour les piles et les files d'attente.

Espérons que cette aide!

67
répondu templatetypedef 2017-07-17 23:29:43

désolé si j'ai mal compris votre question, mais si Je ne l'ai pas fait, que je crois que c'est la réponse que vous recherchez.

avec un vecteur, vous ne pouvez ajouter/supprimer efficacement des éléments à l'extrémité du conteneur. Avec un deque, vous pouvez efficacement Ajouter/Supprimer des éléments au début/fin du conteneur. Avec une liste, vous pouvez efficacement insérer/supprimer des éléments n'importe où dans le conteneur.

vecteurs/deque permettre à accès aléatoire les itérateurs. les listes n'autorisent qu'un accès séquentiel.

comment vous devez utiliser et stocker les données est la façon dont vous déterminez ce qui est le plus approprié.

EDIT:

il y a beaucoup plus à cela, ma réponse est très généralisée. Je peux aller plus en profondeur si je suis encore sur la trace de ce que votre question est à propos.

0
répondu 2011-09-19 21:10:25