vector vs. lista en STL

239

Noté en Effective STL que

vector es el tipo de secuencia que debe usarse por defecto.

¿Qué significa? Parece que ignorar la eficiencia vectorpuede hacer cualquier cosa.

¿Alguien podría ofrecerme un escenario en el vectorque no sea una opción factible pero listdeba usarse?

Skydoor
fuente
66
Aunque no es lo que pidió, vale la pena señalar que el valor predeterminado de vector también significa que puede interactuar fácilmente con código antiguo, bibliotecas C o bibliotecas que no sean plantillas, ya que vector es un envoltorio delgado alrededor de la matriz dinámica "tradicional" de un puntero y tamaño.
18
Bjarne Strostrup realmente hizo una prueba en la que generó números aleatorios y luego los agregó a una lista y un vector respectivamente. Las inserciones se hicieron para que la lista / vector se ordenara en todo momento. Aunque esto es típicamente "dominio de lista", el vector superó a la lista por un margen GRANDE. La razón es que el acceso a la memoria es lento y el almacenamiento en caché funciona mejor para datos secuenciales. Todo está disponible en su discurso de apertura de "GoingNative 2012"
evadiendo
1
Si desea ver la nota clave de Bjarne Stroustrup que @evading mencionó, la encontré aquí: youtu.be/OB-bdWKwXsU?t=2672
brain56

Respuestas:

98

Situaciones en las que desea insertar una gran cantidad de elementos en cualquier lugar que no sea el final de una secuencia repetidamente.

Consulte las garantías de complejidad para cada tipo diferente de contenedor:

¿Cuáles son las garantías de complejidad de los contenedores estándar?

Martin York
fuente
2
Insertar elementos al final también cuenta porque puede conducir a la asignación de memoria y a los costos de copia de elementos. Y también, insertar elenets al comienzo de un vector es casi imposible, listtienepush_front
Notinlist
99
No, la inserción de elementos al final de un vector se amortiza a tiempo constante. Las asignaciones de memoria solo ocurren ocasionalmente, y puede preasignar el vector para evitarlo. Por supuesto, si DEBE haber garantizado inserciones constantes de tiempo constante, supongo que esto sigue siendo un problema.
Brian
16
@Notinlist - ¿Es el siguiente "casi imposible" para ti? v.insert (v.begin (), i)
Manuel
55
@Notinlist: estoy de acuerdo con usted, es solo que no quería que el OP piense que la interfaz no está allí en caso de que uno quiera dispararse en el pie (de rendimiento).
Manuel
16
Bjarne Strostrup realmente hizo una prueba en la que generó números aleatorios y luego los agregó a una lista y un vector respectivamente. Las inserciones se hicieron para que la lista / vector se ordenara en todo momento. Aunque esto es típicamente "dominio de lista", el vector superó a la lista por un margen GRANDE. La razón es que el acceso a la memoria es lento y el almacenamiento en caché funciona mejor para datos secuenciales. Todo está disponible en su discurso de apertura de "GoingNative 2012"
evadiendo
410

vector:

  • Memoria contigua
  • Preasigna espacio para elementos futuros, por lo que se requiere espacio adicional más allá de lo necesario para los elementos mismos.
  • Cada elemento solo requiere el espacio para el tipo de elemento en sí (sin punteros adicionales).
  • Puede reasignar memoria para todo el vector cada vez que agrega un elemento.
  • Las inserciones al final son constantes, el tiempo amortizado, pero las inserciones en otros lugares son costosas O (n).
  • Los borrados al final del vector son de tiempo constante, pero para el resto es O (n).
  • Puede acceder aleatoriamente a sus elementos.
  • Los iteradores se invalidan si agrega o elimina elementos al vector o desde él.
  • Puede obtener fácilmente la matriz subyacente si necesita una matriz de los elementos.

lista:

  • Memoria no contigua.
  • No hay memoria preasignada. La sobrecarga de memoria para la lista en sí es constante.
  • Cada elemento requiere espacio adicional para el nodo que contiene el elemento, incluidos punteros a los elementos siguientes y anteriores de la lista.
  • Nunca tiene que reasignar memoria para toda la lista solo porque agrega un elemento.
  • Las inserciones y borrados son baratos, sin importar en qué parte de la lista ocurran.
  • Es barato combinar listas con empalmes.
  • No puede acceder aleatoriamente a los elementos, por lo que llegar a un elemento en particular en la lista puede ser costoso.
  • Los iteradores siguen siendo válidos incluso cuando agrega o elimina elementos de la lista.
  • Si necesita un conjunto de elementos, deberá crear uno nuevo y agregarlos todos, ya que no hay un conjunto subyacente.

En general, use el vector cuando no le importa qué tipo de contenedor secuencial está utilizando, pero si está haciendo muchas inserciones o borrados hacia y desde cualquier parte del contenedor que no sea el final, querrá para usar la lista. O si necesita acceso aleatorio, entonces querrá un vector, no una lista. Aparte de eso, naturalmente hay casos en los que necesitará uno u otro según su aplicación, pero en general, esas son buenas pautas.

Jonathan M Davis
fuente
2
Además, la asignación desde la tienda gratuita no es gratuita. :) Agregar nuevos elementos a un vector realiza asignaciones de tiendas gratuitas O (log n), pero puede llamar reserve()para reducir eso a O (1). Agregar nuevos elementos a una lista (es decir, no empalmarlos) realiza O (n) asignaciones de tiendas gratuitas.
bk1e
77
Otra consideración es que listlibera memoria cuando borra elementos, pero vectorno lo hace. A vectorno reduce su capacidad cuando reduce su tamaño, a menos que use el swap()truco.
bk1e
@ bk1e: Realmente quiero saber tu truco en reserve () y swap () :)
Dzung Nguyen
2
@nXqd: si necesita agregar N elementos a un vector, llame a v.reserve (v.size () + N) para que realice solo una asignación de tienda gratuita. El truco swap () está aquí: stackoverflow.com/questions/253157/how-to-downsize-stdvector
bk1e
1
@simplename No. Lo que dice es correcto. vector asigna espacio adicional más allá del espacio para los elementos actualmente en el vector; esa capacidad adicional se usa para hacer crecer el vector, de modo que al crecer se amortiza O (1).
Jonathan M Davis
35

Si no necesita insertar elementos con frecuencia, un vector será más eficiente. Tiene una localidad de caché de CPU mucho mejor que una lista. En otras palabras, acceder a un elemento hace muy probable que el siguiente elemento esté presente en la memoria caché y pueda recuperarse sin tener que leer RAM lenta.

Hans Passant
fuente
32

La mayoría de las respuestas aquí pierden un detalle importante: ¿para qué?

¿Qué quieres mantener en el contactor?

Si se trata de una colección de ints, entonces std::listperderá en todos los escenarios, sin tener en cuenta si se puede reasignar, sólo se quita desde el frente, etc. Las listas son más lentas a través, cada inserción le cuesta una interacción con el asignador. Sería extremadamente difícil preparar un ejemplo, donde list<int>late vector<int>. E incluso entonces, deque<int>puede ser mejor o cercano, no justificando el uso de listas, que tendrán una mayor sobrecarga de memoria.

Sin embargo, si se trata de grandes y feos bloques de datos, y pocos de ellos, no desea sobreasignarlos al insertarlos y copiarlos debido a la reasignación sería un desastre; entonces, tal vez, sea mejor que tenga list<UglyBlob>que vector<UglyBlob>.

Aún así, si cambia vector<UglyBlob*>ao incluso vector<shared_ptr<UglyBlob> >, nuevamente, la lista se retrasará.

Entonces, el patrón de acceso, el recuento de elementos objetivo, etc., todavía afecta la comparación, pero en mi opinión, el tamaño de los elementos, el costo de la copia, etc.

Tomasz Gandor
fuente
1
Una reflexión más, que tuve al leer "STL eficaz" por Meyers: una propiedad peculiar de list<T>la posibilidad de spliceen O (1) . Si necesita un empalme de tiempo constante, la lista también puede ser la estructura elegida;)
Tomasz Gandor
1 - Ni siquiera tiene que ser un UglyBlob- incluso un objeto con sólo unos pocos miembros de cuerda pueden ser fácilmente prohibitivamente caro para copiar, por lo que las reasignaciones serán costar. Además: no descuide el espacio superior que vectorpuede causar el crecimiento exponencial de un objeto de retención de unas pocas docenas de bytes de tamaño (si no puede hacerlo reservede antemano).
Martin Ba
En cuanto a vector<smart_ptr<Large>>vs. list<Large>, diría que si necesita acceso aleatorio a los elementos, vectortiene sentido. Si no necesita acceso aleatorio, listparece más simple y debería funcionar por igual.
Martin Ba
19

Una capacidad especial de std :: list es el empalme (vincular o mover parte o una lista completa a una lista diferente).

O tal vez si sus contenidos son muy caros de copiar. En tal caso, podría ser más barato, por ejemplo, ordenar la colección con una lista.

También tenga en cuenta que si la colección es pequeña (y el contenido no es particularmente costoso de copiar), un vector aún podría superar a una lista, incluso si inserta y borra en cualquier lugar. Una lista asigna cada nodo individualmente, y eso podría ser mucho más costoso que mover algunos objetos simples.

No creo que haya reglas muy estrictas. Depende de lo que desee hacer principalmente con el contenedor, así como de qué tan grande espere que sea el contenedor y el tipo contenido. Un vector generalmente triunfa sobre una lista, porque asigna su contenido como un solo bloque contiguo (es básicamente una matriz asignada dinámicamente, y en la mayoría de las circunstancias, una matriz es la forma más eficiente de contener un montón de cosas).

TíoBens
fuente
1
+1. El empalme se pasa por alto, pero desafortunadamente no es un tiempo constante como se desea. : (((No puede ser si list :: size es de tiempo constante.)
Estoy bastante seguro de que list :: size es (permitido ser) lineal por esta misma razón.
UncleBens
1
@Roger: list::sizeno es necesariamente tiempo constante. Ver stackoverflow.com/questions/228908/is-listsize-really-on y gcc.gnu.org/ml/libstdc++/2005-11/msg00219.html
Potatoswatter
@Potatoswatter: Que el estándar sea vago y que, por lo tanto, no pueda confiar en implementaciones "conformes", lo hace aún más problemático. Literalmente, debe evitar el stdlib para obtener una garantía portátil y confiable.
@Roger: sí, desafortunadamente. Mi proyecto actual depende en gran medida de las operaciones de empalme y esa estructura es casi recta C. Aún más desafortunadamente, en la secuencia N3000 spliceentre diferentes listas se especifica como complejidad lineal y sizees específicamente constante. Por lo tanto, para acomodar a los principiantes que iteran sizeo lo que sea, una clase completa de algoritmos está fuera del alcance de la STL o cualquier período de contenedores "conformes".
Potatoswatter
13

Bueno, los estudiantes de mi clase parecen incapaces de explicarme cuándo es más efectivo usar vectores, pero se ven muy felices cuando me aconsejan que use listas.

Así es como lo entiendo

Liza : cada elemento contiene una dirección para el elemento siguiente o anterior, por lo que con esta función, puede aleatorizar los elementos, incluso si no están ordenados, el orden no cambiará: es eficiente si su memoria está fragmentada. Pero también tiene otra gran ventaja: puede insertar / eliminar elementos fácilmente, porque lo único que debe hacer es cambiar algunos punteros. Inconveniente: para leer un elemento individual aleatorio, debe saltar de un elemento a otro hasta encontrar la dirección correcta.

Vectores : cuando se usan vectores, la memoria está mucho más organizada como las matrices regulares: cada elemento n se almacena justo después del elemento (n-1) y antes del elemento (n + 1). ¿Por qué es mejor que la lista? Porque permite un acceso aleatorio rápido. Aquí se explica cómo: si conoce el tamaño de un elemento en un vector y si están contiguos en la memoria, puede predecir fácilmente dónde está el enésimo elemento; no tiene que examinar todo el elemento de una lista para leer el que desea, con vector, lo lee directamente, con una lista que no puede. Por otro lado, modificar la matriz de vectores o cambiar un valor es mucho más lento.

Las listas son más apropiadas para realizar un seguimiento de los objetos que se pueden agregar / eliminar en la memoria. Los vectores son más apropiados cuando desea acceder a un elemento desde una gran cantidad de elementos individuales.

No sé cómo se optimizan las listas, pero debe saber que si desea un acceso de lectura rápido, debe usar vectores, porque cuán bueno es el STL para sujetar las listas, no será tan rápido en el acceso de lectura como el vector.

jokoon
fuente
"modificar la matriz de vectores o cambiar un valor es mucho más lento", como se lee, esto parece contradecir lo que dijo justo antes de que el vector está predispuesto a un buen rendimiento debido a su naturaleza de bajo nivel y contigua. ¿Quiso decir que reasignar lo vectorcausado por cambiar su tamaño puede ser lento? luego acordó, pero en los casos en que reserve()se puede utilizar, eso evita esos problemas.
underscore_d
10

Básicamente, un vector es una matriz con gestión automática de memoria. Los datos son contiguos en la memoria. Intentar insertar datos en el medio es una operación costosa.

En una lista, los datos se almacenan en ubicaciones de memoria no relacionadas. Insertar en el medio no implica copiar algunos de los datos para dejar espacio para el nuevo.

Para responder más específicamente a su pregunta, citaré esta página

los vectores son generalmente los más eficientes en el tiempo para acceder a los elementos y para agregar o eliminar elementos del final de la secuencia. Para las operaciones que implican insertar o eliminar elementos en posiciones distintas al final, funcionan peor que las deques y las listas, y tienen iteradores y referencias menos consistentes que las listas.

f4.
fuente
9

En cualquier momento no se pueden invalidar los iteradores.

Dirkgently
fuente
2
Pero nunca salte a esa conclusión acerca de los iteradores sin preguntar si bastarían las referencias persistentes a un deque.
Potatoswatter
8

Cuando tienes mucha inserción o eliminación en el medio de la secuencia. Por ejemplo, un administrador de memoria.

AraK
fuente
así que la diferencia entre ellos es solo la eficiencia, no el problema funcional.
skydoor
Ambos modelan una secuencia de elementos, por supuesto. Hay una pequeña diferencia en el uso, como lo menciona @dirkgently, pero hay que tener en cuenta la complejidad de sus operaciones "a menudo hechas" para decidir qué secuencia elegir (respuesta de @Martin).
AraK
@skydoor: hay algunas diferencias funcionales. Por ejemplo, solo el vector admite acceso aleatorio (es decir, puede indexarse).
Manuel
2
@skydoor: la eficiencia se traduce en rendimiento. El bajo rendimiento puede arruinar la funcionalidad. El rendimiento es la ventaja de C ++, después de todo.
Potatoswatter
4

Preservar la validez de los iteradores es una razón para usar una lista. Otra es cuando no desea que un vector se reasigne al insertar elementos. Esto se puede gestionar mediante un uso inteligente de reserve (), pero en algunos casos puede ser más fácil o más factible usar una lista.

John Dibling
fuente
4

Cuando desee mover objetos entre contenedores, puede usarlos list::splice.

Por ejemplo, un algoritmo de partición de gráficos puede tener un número constante de objetos divididos recursivamente entre un número creciente de contenedores. Los objetos deben inicializarse una vez y permanecer siempre en las mismas ubicaciones en la memoria. Es mucho más rápido reorganizarlos volviendo a vincular que reasignando.

Editar: a medida que las bibliotecas se preparan para implementar C ++ 0x, el caso general de empalmar una subsecuencia en una lista se está convirtiendo en una complejidad lineal con la longitud de la secuencia. Esto se debe a que splice(ahora) necesita iterar sobre la secuencia para contar la cantidad de elementos que contiene. (Debido a que la lista necesita registrar su tamaño). Simplemente contar y volver a vincular la lista es aún más rápido que cualquier alternativa, y empalmar una lista completa o un solo elemento son casos especiales con complejidad constante. Pero, si tiene secuencias largas para empalmar, es posible que tenga que buscar un contenedor mejor, anticuado y no compatible.

Agua de patata
fuente
2

La única regla estricta donde listdebe usarse es donde necesita distribuir punteros a elementos del contenedor.

A diferencia de vector, usted sabe que la memoria de los elementos no se reasignará. Si pudiera ser así, podría tener punteros a la memoria no utilizada, que es, en el mejor de los casos, un gran no-no y, en el peor de los casos, a SEGFAULT.

(Técnicamente, una vectorde *_ptrtambién funcionaría, pero en ese caso estás emulando, listasí que eso es solo semántica).

Otras reglas suaves tienen que ver con los posibles problemas de rendimiento de insertar elementos en el medio de un contenedor, por lo listque sería preferible.

iPherian
fuente
2

Hágalo simple:
al final del día, cuando esté confundido al elegir contenedores en C ++, use esta imagen de diagrama de flujo (Dígame gracias): ingrese la descripción de la imagen aquí

Vector-
1. el vector se basa en la memoria contagiosa
2. el vector es el camino a seguir para un conjunto de datos pequeño
3. el vector funciona más rápido mientras se recorre en el conjunto de datos
4. la eliminación de inserción del vector es lenta en un conjunto de datos enorme pero rápido para muy pequeños

Lista-
1. la lista se basa en la memoria de almacenamiento dinámico
2. la lista es el camino a seguir para un conjunto de datos muy grande
3. la lista es relativamente lenta al atravesar un conjunto de datos pequeño pero rápido en un conjunto de datos enorme
4. la eliminación de la inserción de la lista es rápida gran conjunto de datos pero lento en los más pequeños

xyz xyz
fuente
1

Las listas son solo un contenedor para una lista doblemente enlazada en stl, por lo que ofrecen una característica que puede esperar de d-linklist, a saber, inserción y eliminación de O (1). Mientras que los vectores son una secuencia de datos contagiosa que funciona como una matriz dinámica. PS: más fácil de atravesar.

Ritankar Paul
fuente
0

En el caso de un vector y una lista , las principales diferencias que me destacan son las siguientes:

vector

  • Un vector almacena sus elementos en la memoria contigua. Por lo tanto, el acceso aleatorio es posible dentro de un vector, lo que significa que acceder a un elemento de un vector es muy rápido porque simplemente podemos multiplicar la dirección base con el índice del elemento para acceder a ese elemento. De hecho, solo se necesita O (1) o tiempo constante para este propósito.

  • Dado que un vector básicamente envuelve una matriz, cada vez que inserta un elemento en el vector (matriz dinámica), tiene que cambiar su tamaño encontrando un nuevo bloque contiguo de memoria para acomodar los nuevos elementos que es costoso en tiempo.

  • No consume memoria adicional para almacenar punteros a otros elementos dentro de él.

lista

  • Una lista almacena sus elementos en memoria no contigua. Por lo tanto, el acceso aleatorio no es posible dentro de una lista, lo que significa que para acceder a sus elementos tenemos que usar los punteros y recorrer la lista que es más lenta en relación con el vector. Esto toma O (n) o tiempo lineal que es más lento que O (1).

  • Como una lista usa memoria no contigua, el tiempo necesario para insertar un elemento dentro de una lista es mucho más eficiente que en el caso de su contraparte vectorial porque se evita la reasignación de memoria.

  • Consume memoria extra para almacenar punteros al elemento antes y después de un elemento en particular.

Por lo tanto, teniendo en cuenta estas diferencias, generalmente consideramos la memoria , el acceso aleatorio frecuente y la inserción para decidir el ganador del vector frente a la lista en un escenario dado.

Codificador ardiente
fuente
0

La lista es una lista doblemente vinculada, por lo que es fácil insertar y eliminar un elemento. Solo tenemos que cambiar los pocos punteros, mientras que en el vector si queremos insertar un elemento en el medio, cada elemento después de que tenga que cambiar un índice. Además, si el tamaño del vector está lleno, primero debe aumentar su tamaño. Por lo tanto, es una operación costosa. Por lo tanto, siempre que se requiera que las operaciones de inserción y eliminación se realicen con más frecuencia en dicha lista de casos, se debe utilizar.

Siddesh Pawar
fuente