2012-01-05 24 views
13

Me gustaría utilizar std::forward_liststd :: forward_list y std :: :: forward_list push_back

Porque:

lista Forward es un contenedor que soporta la rápida inserción y extracción de elementos desde cualquier lugar desde el contenedor

Pero no hay implementación * std :: forward_list :: push_back *.

¿Existe alguna forma de alto rendimiento para agregar soporte por una o ninguna razón para hacerlo?

+4

Tenga en cuenta que la 'std :: list' bidireccional también admite" inserción y eliminación rápida de elementos desde cualquier lugar del contenedor ", y tiene' push_back'. El costo es un puntero extra por entrada. ¿Es la memoria tan apretada que no puedes usar eso? –

+0

¿Por qué lo necesitas? ¿Quieres hacer crecer tu lista en ambos sentidos? ¿No puedes usar 'push_front()' con la misma facilidad? –

+0

Quiero seguir ordenando la lista –

Respuesta

16

std::forward_list admite la inserción y eliminación rápida, pero no transversal al final. Para implementar .push_back, primero debe llegar al final de la lista, que es O (N) y no rápido en absoluto, que es probablemente la razón por la que no está implementado.

 

Se podía encontrar el iterador al último elemento incrementando .before_begin N veces

auto before_end = slist.before_begin(); 
for (auto& _ : slist) 
    ++ before_end; 

y luego usar .insert_after o .emplace_after para insertar el elemento:

slist.insert_after(before_end, 1234); 
+12

Por supuesto, una' forward_list' podría modificarse almacenando un iterador adicional apuntando a su final. De esa forma, 'push_back' podría hacerse en O (1) vez. –

+0

¿No hay ninguna razón para la implementación 'push_back'? –

+0

@larsmans: creo que se convertirá en una 'std :: list'. – kennytm

6

Hay no push_back porque la lista no realiza un seguimiento de la parte posterior de la lista , solo el frente.

Se puede escribir una envoltura alrededor de la lista que mantiene un iterador al último elemento, e implementa push_back utilizando insert_after o push_front dependiendo de si la lista está vacía. Esto se volverá bastante complicado si desea admitir las operaciones más complejas (por ejemplo, sort y splice_after).

Alternativamente, si no necesita push_back para ser rápido, es sencillo hacerlo en tiempo lineal.

A menos que la memoria sea extremadamente estrecha, la mejor solución es usar list. Tiene las mismas características de rendimiento que forward_list, y es bidireccional, admite push_back y push_front; el costo es un puntero extra por elemento.

+0

+1, solo mi idea. 'sort' no sería tan difícil, por cierto; simplemente ordena la 'forward_list' subyacente y encuentra el nuevo final. Como la clasificación es O (* n * lg * n *), domina el hallazgo final. –

+0

@larsmans: Eso es verdad. Sin embargo, podría ser más complicado 'empalmar' en tiempo constante (si está empalmando una 'lista_de_envío' normal que no sigue su final). –

+0

Sí, 'empalmar' sería un problema para hacerlo bien. –

5

El punto de std :: forward_list es ser una versión ultra-stripped de std :: list, por lo que no almacena un iterador en el último elemento. Si necesita uno, usted tiene que mantener por sí mismo, así:

forward_list<int> a; 

auto it = a.before_begin(); 

for(int i = 0; i < 10; ++i) 
    it = a.insert_after(it, i); 
26

recomiendo contra std::forward_list al igual que yo recomiendo en contra std::list en casi todas las situaciones. Personalmente, nunca encontré una situación en mi código en la que una lista vinculada fuera la mejor estructura de datos.

En C++, su recopilación de datos predeterminada debe ser std::vector. Le da eficiente push_back, si eso es lo que realmente necesita. Desde el punto de vista técnico, no le ofrece una eliminación e inserción eficiente si solo observa las medidas abstractas de complejidad de gran O de esa operación.En el mundo real, sin embargo, std::vector todavía gana incluso para insertar y eliminar en el medio.

Como ejemplo, Bjarne Stroustrup creó una prueba de un elemento std::list de 100,000 frente a std::vector. Buscaría cada uno de un elemento y lo eliminaría. Luego, encontraría un punto de inserción e insertaría en el centro. Podría haber usado una búsqueda binaria en el std::vector, pero no hizo la comparación "más justa".

Los resultados muestran una victoria fuerte para std::vector, incluso en esta situación donde se supone que std::list es fuerte. Simplemente atravesando el std::list toma mucho más tiempo debido a cuán separados están en la memoria todos los objetos. std::list no es compatible con caché, que posiblemente sea lo más importante para los procesadores modernos.

The complete talk by Bjarne Stroustrup

Thorough explanation of the effects, with benchmarks at multiple sizes

Tenga en cuenta que este segundo enlace aquí da algunas situaciones de donde posiblemente puede que desee utilizar un std::list, por ejemplo, cuando el tamaño de los elementos es grande. Sin embargo, he estado en una situación en la que tengo muchos elementos en un orden particular y necesito eliminar algunos.

Estos elementos eran más grandes que cualquier tipo incorporado, pero no grandes, quizás 20-30 bytes cada uno en una computadora de 32 bits). La cantidad de elementos era lo suficientemente grande para que toda mi estructura de datos tuviera unos cientos de MiB. La recopilación de datos fue un conjunto de valores que teóricamente podrían ser válidos según la información actualmente conocida. El algoritmo iteraba sobre todos los elementos y eliminaba los elementos que ya no podían ser válidos en función de la información nueva, con cada pase probablemente eliminando alrededor del 80% de los elementos restantes.

Mi primera implementación fue un enfoque directo std::vector donde eliminé elementos no válidos a medida que atravesaba. Esto funcionó para pequeños conjuntos de datos de prueba, pero cuando traté de hacer algo real, fue demasiado lento para ser útil. Cambié a std::list como contenedor, pero usé el mismo algoritmo y vi mejoras significativas en el rendimiento. Sin embargo, todavía era demasiado lento para ser útil. El cambio ganador fue volver a cambiar a std::vector, pero en lugar de eliminar elementos que estaban mal, creé un nuevo std::vector, y todos los elementos que encontré que eran buenos se pusieron en ese std::vector, y luego al final de la función Simplemente descartaría el viejo std::vector y usaría el nuevo, y esto me dio una aceleración tan grande como la std::list como la me dio sobre la implementación original de std::vector, y esto fue lo suficientemente rápido como para ser útil.

+0

O .. mantenga una marca "inactiva" en cada elemento que actualice en lugar de eliminar elementos, solo compacte el vector cuando la cantidad de elementos muertos alcance un cierto límite. No funcionará con tipos primitivos en el vector, por supuesto, pero la respuesta es muy interesante, ta. – gbjbaanb

+1

Bonito despotricar, pero no veo cómo esto responde la pregunta en absoluto. Hay muchas situaciones en las que una lista vinculada por separado funcionará bien; de hecho, creo que cualquier implementación detrás de escena utiliza esencialmente una lista de fotogramas vinculados para representar la pila de tiempo de ejecución dentro de un único hilo (los fotogramas pueden ser consecutivos en la memoria, pero no son del mismo tamaño, por lo que no pueden abordarse directamente como en un vector) Normalmente, si solo necesita una estructura simple de pila o cola, puede implementarla fácilmente utilizando una lista (y un puntero adicional a su extremo en caso de una cola). –

+2

@MarcvanLeeuwen Resuelve el problema en lugar de responder la pregunta. El problema es que quieren una estructura de datos de alto rendimiento para insertar y eliminar. 'std :: forward_list' rara vez es la estructura de datos para lograr objetivos tan amplios. En cuanto a su ejemplo específico, 'std :: forward_list' tampoco sirve de nada (a menos que sea un malentendido, que bien podría ser). Si los marcos no son del mismo tamaño, eso implica una asignación dinámica. Una lista vinculada de punteros casi nunca es una mejor estructura de datos que un vector de punteros. –

1

Desafortunadamente no puedo agregar un comentario (baja reputación) pero solo quería mencionar que una de las ventajas de la lista forward_list & es que las operaciones de inserción-eliminación no invalidan los iteradores. Tenía una aplicación donde la colección de elementos crecía mientras iteraba y procesaba elementos individuales. La ausencia de invalidación de iterador me permitió implementar escaneo de segmento (comienzo de la lista como inicio del segmento y comienzo del último segmento como final).

Cuestiones relacionadas