web-dev-qa-db-fra.com

vecteur vs. liste en STL

J'ai remarqué dans Effective STL que 

le vecteur est le type de séquence que devrait être utilisé par défaut.

Ça veut dire quoi? Il semble qu'ignorer l'efficacité vector peut faire n'importe quoi. 

Quelqu'un pourrait-il me proposer un scénario dans lequel vector n'est pas une option réalisable mais list doit être utilisé?

199
skydoor

Situations dans lesquelles vous souhaitez insérer de nombreux éléments n'importe où, sauf la fin d'une séquence à plusieurs reprises.

Découvrez les garanties de complexité pour chaque type de conteneur:

Quelles sont les garanties de complexité des conteneurs standard?

83
Martin York

vecteur:

  • Mémoire contiguë.
  • Pré-allouez de la place pour les futurs éléments. Il faut donc plus d’espace que ce qui est nécessaire pour les éléments eux-mêmes.
  • Chaque élément requiert uniquement l'espace pour le type d'élément lui-même (pas de pointeurs supplémentaires).
  • Peut réaffecter de la mémoire pour tout le vecteur à tout moment où vous ajoutez un élément.
  • Les insertions à la fin sont constantes, le temps amorti, mais les insertions ailleurs sont un O (n) coûteux.
  • Les effacements à la fin du vecteur sont constants, mais pour le reste, il s'agit de O (n).
  • Vous pouvez accéder de manière aléatoire à ses éléments.
  • Les itérateurs sont invalidés si vous ajoutez ou supprimez des éléments du vecteur.
  • Vous pouvez facilement accéder au tableau sous-jacent si vous avez besoin d'un tableau d'éléments.

liste:

  • Mémoire non contiguë.
  • Pas de mémoire préallouée. La surcharge de mémoire pour la liste elle-même est constante.
  • Chaque élément nécessite un espace supplémentaire pour le nœud qui le contient, y compris des pointeurs vers les éléments suivant et précédent de la liste.
  • Il n'est jamais nécessaire de réallouer de la mémoire pour toute la liste simplement parce que vous ajoutez un élément.
  • Les insertions et les effacements sont peu coûteux, peu importe où ils se produisent dans la liste.
  • Combiner des listes avec des épissures n’est pas cher.
  • Vous ne pouvez pas accéder aux éléments de manière aléatoire. Par conséquent, il peut être coûteux de consulter un élément particulier de la liste.
  • Les itérateurs restent valables même lorsque vous ajoutez ou supprimez des éléments de la liste.
  • Si vous avez besoin d'un tableau d'éléments, vous devez en créer un nouveau et tous les ajouter, car il n'y a pas de tableau sous-jacent.

En général, utilisez vector lorsque vous ne vous souciez pas du type de conteneur séquentiel que vous utilisez, mais si vous effectuez de nombreuses insertions ou effacements entre le conteneur et la fin de celui-ci, vous allez vouloir utiliser la liste. Ou si vous avez besoin d'un accès aléatoire, alors vous allez vouloir un vecteur, pas une liste. Autre que cela, il y a naturellement des cas où vous aurez besoin de l'un ou de l'autre en fonction de votre application, mais en général, ce sont de bonnes directives.

364
Jonathan M Davis

Si vous n'avez pas souvent besoin d'insérer des éléments, un vecteur sera plus efficace. Il possède une bien meilleure localisation de cache CPU qu'une liste. En d'autres termes, l'accès à un élément rend très probable que l'élément suivant soit présent dans le cache et puisse être récupéré sans avoir à lire une RAM lente.

27
Hans Passant

La plupart des réponses ici manquent un détail important: à quoi sert-il?

Que voulez-vous conserver dans le conteneur?

S'il s'agit d'une collection de ints, alors std::list perdra dans tous les scénarios, que vous puissiez ou non réallouer, vous ne supprimez que du début, etc. Il serait extrêmement difficile de préparer un exemple où list<int> bat vector<int>. Et même dans ce cas, deque<int> peut être meilleur ou plus proche, et ne consiste pas seulement à utiliser des listes, ce qui entraînera une surcharge de mémoire.

Cependant, si vous avez affaire à de gros blocs de données laids, et à quelques-uns d'entre eux, vous ne souhaitez pas sur-localisation lors de l'insertion, et la copie en raison d'une réaffectation serait un désastre. list<UglyBlob> que vector<UglyBlob>.

Cependant, si vous passez à vector<UglyBlob*> ou même à vector<shared_ptr<UglyBlob> >, la liste sera à la traîne.

Ainsi, le modèle d'accès, le nombre d'éléments cibles, etc. affectent toujours la comparaison, mais à mon avis - la taille des éléments - le coût de la copie, etc.

23
Tomasz Gandor

Une fonctionnalité spéciale de std :: list est le splicing (relier ou déplacer une partie ou une liste entière dans une liste différente).

Ou peut-être si votre contenu est très coûteux à copier. Dans un tel cas, il pourrait être moins coûteux, par exemple, de trier la collection avec une liste.

Notez également que si la collection est petite (et que le contenu n'est pas particulièrement coûteux à copier), un vecteur peut toujours surperformer une liste, même si vous insérez et effacez-le n'importe où. Une liste alloue chaque nœud individuellement, ce qui peut coûter beaucoup plus cher que de déplacer quelques objets simples.

Je ne pense pas qu'il y ait des règles très strictes. Cela dépend de ce que vous voulez faire avec le conteneur, ainsi que de la taille du conteneur et du type contenu. Un vecteur l'emporte généralement sur une liste, car il attribue son contenu sous la forme d'un seul bloc contigu (il s'agit essentiellement d'un tableau alloué de manière dynamique et, dans la plupart des cas, un tableau est le moyen le plus efficace de contenir un tas de choses).

15
UncleBens

Eh bien, les étudiants de ma classe semblent incapables de m'expliquer quand il est plus efficace d'utiliser des vecteurs, mais ils ont l'air assez heureux quand ils me conseillent d'utiliser des listes.

C'est comme ça que je le comprends

Listes : Chaque élément contient une adresse pour l'élément suivant ou précédent, de sorte qu'avec cette fonctionnalité, vous pouvez randomiser les éléments, même s'ils ne sont pas triés, l'ordre ne changera pas: c'est efficace si votre mémoire est fragmentée . Mais elle a aussi un autre très gros avantage: vous pouvez facilement insérer/supprimer des éléments, car la seule chose à faire est de changer quelques pointeurs . Inconvénient: Lire un élément aléatoire, vous devez passer d’un élément à l’autre jusqu’à ce que vous trouviez la bonne adresse.

Vectors : Lorsque vous utilisez des vecteurs, la mémoire est beaucoup plus organisée que les tableaux classiques: chaque nième élément est stocké juste après (n-1) élément et avant (n + 1) élément Pourquoi est-il meilleur que list? ?__. Parce qu'il permet un accès aléatoire rapide . Voici comment: si vous connaissez la taille d'un élément dans un vecteur, et s'ils sont contigus en mémoire, vous pouvez facilement prédire où se trouve le nième élément; vous n'avez pas besoin de parcourir tous les éléments d'une liste pour lire celui que vous voulez, avec un vecteur, vous le lisez directement, avec une liste que vous ne pouvez pas .. .. D'autre part, modifiez le tableau de vecteurs ou modifiez-le. une valeur est beaucoup plus lente.

Les listes sont plus appropriées pour garder la trace des objets qui peuvent être ajoutés/supprimés en mémoire . Les vecteurs sont plus appropriés lorsque vous souhaitez accéder à un élément à partir d'une grande quantité d'éléments uniques.

Je ne sais pas comment les listes sont optimisées, mais vous devez savoir que si vous voulez un accès rapide en lecture, vous devez utiliser des vecteurs, car la qualité de la STL pour raccourcir les listes ne sera pas aussi rapide en lecture-accès que vector.

13
jokoon

Fondamentalement, un vecteur est un tableau avec gestion automatique de la mémoire. Les données sont contiguës en mémoire. Essayer d'insérer des données au milieu est une opération coûteuse. 

Dans une liste, les données sont stockées dans des emplacements de mémoire non liés. L’insertion au milieu n’implique pas la copie de certaines données pour laisser de la place au nouveau.

Pour répondre plus précisément à votre question, je citerai cette page

les vecteurs sont généralement les plus efficaces dans le temps pour accéder aux éléments et pour ajouter ou supprimer des éléments à la fin de la séquence. Pour les opérations impliquant l’insertion ou le retrait d’éléments à des positions autres que la fin, leurs performances sont inférieures à celles de deques et de listes, et leurs itérateurs et références sont moins cohérents que les listes.

9
f4.

Chaque fois que vous ne pouvez pas faire invalider les itérateurs.

9
dirkgently

Lorsque vous avez beaucoup d'insertion ou de suppression au milieu de la séquence. par exemple. un gestionnaire de mémoire.

8
AraK

Préserver la validité des itérateurs est l'une des raisons d'utiliser une liste. Une autre possibilité est de ne pas vouloir réaffecter un vecteur lors de l’ajout d’éléments. Cela peut être géré par une utilisation intelligente de reserve (), mais dans certains cas, il pourrait être plus facile ou plus pratique de simplement utiliser une liste.

4
John Dibling

Lorsque vous souhaitez déplacer des objets entre des conteneurs, vous pouvez utiliser list::splice.

Par exemple, un algorithme de partitionnement de graphes peut avoir un nombre constant d'objets divisés de manière récursive parmi un nombre croissant de conteneurs. Les objets doivent être initialisés une fois et rester toujours aux mêmes emplacements en mémoire. Il est beaucoup plus rapide de les réorganiser en les rapprochant qu'en les réaffectant.

Édition: Alors que les bibliothèques se préparent à implémenter C++ 0x, le cas général d’épissage d’une sous-séquence en une liste devient une complexité linéaire avec la longueur de la séquence. En effet, splice (maintenant) doit parcourir la séquence pour compter le nombre d'éléments qu'il contient. (Parce que la liste doit enregistrer sa taille.) Compter et relier la liste est plus rapide que toute autre solution, et épisser une liste entière ou un seul élément est un cas particulier de complexité constante. Toutefois, si vous devez assembler de longues séquences, il vous faudra peut-être chercher un conteneur plus adapté, ancien et non conforme.

4
Potatoswatter

La seule règle stricte dans laquelle list doit être utilisé est celle dans laquelle vous devez répartir les pointeurs sur les éléments du conteneur. 

Contrairement à vector, vous savez que la mémoire des éléments ne sera pas réallouée. Si tel est le cas, vous pourriez avoir des pointeurs sur la mémoire inutilisée, ce qui est au mieux un gros non-non et au pire une SEGFAULT.

(Techniquement, une vector de *_ptr fonctionnerait aussi, mais dans ce cas, vous émulez list, ce n'est donc qu'une sémantique.)

D'autres règles souples concernent les problèmes de performances possibles liés à l'insertion d'éléments au milieu d'un conteneur, list étant préférable. 

0
iPherian