¿Cuándo debo usar una lista frente a una lista enlazada?

Respuestas:

107

Editar

Por favor lea los comentarios a esta respuesta. La gente dice que no hice las pruebas adecuadas. Estoy de acuerdo en que esta no debería ser una respuesta aceptada. Mientras aprendía, hice algunas pruebas y sentí ganas de compartirlas.

Respuesta original ...

Encontré resultados interesantes:

// Temporary class to show the example
class Temp
{
    public decimal A, B, C, D;

    public Temp(decimal a, decimal b, decimal c, decimal d)
    {
        A = a;            B = b;            C = c;            D = d;
    }
}

Lista vinculada (3,9 segundos)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.AddLast(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Lista (2,4 segundos)

        List<Temp> list = new List<Temp>(); // 2.4 seconds

        for (var i = 0; i < 12345678; i++)
        {
            var a = new Temp(i, i, i, i);
            list.Add(a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

¡Incluso si solo accedes a los datos esencialmente, es mucho más lento! Yo digo que nunca uses una lista enlazada.




Aquí hay otra comparación que realiza muchas inserciones (planeamos insertar un elemento en el medio de la lista)

Lista vinculada (51 segundos)

        LinkedList<Temp> list = new LinkedList<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            var curNode = list.First;

            for (var k = 0; k < i/2; k++) // In order to insert a node at the middle of the list we need to find it
                curNode = curNode.Next;

            list.AddAfter(curNode, a); // Insert it after
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Lista (7,26 segundos)

        List<Temp> list = new List<Temp>();

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.Insert(i / 2, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Lista vinculada con referencia de ubicación donde insertar (.04 segundos)

        list.AddLast(new Temp(1,1,1,1));
        var referenceNode = list.First;

        for (var i = 0; i < 123456; i++)
        {
            var a = new Temp(i, i, i, i);

            list.AddLast(a);
            list.AddBefore(referenceNode, a);
        }

        decimal sum = 0;
        foreach (var item in list)
            sum += item.A;

Entonces, solo si planea insertar varios elementos y también en algún lugar tiene la referencia de dónde planea insertar el elemento, use una lista vinculada. El hecho de que tenga que insertar muchos elementos no lo hace más rápido, ya que la búsqueda de la ubicación en la que desea insertarlo lleva tiempo.

Tono Nam
fuente
99
LinkedList tiene una ventaja sobre List (esto es específico de .net): dado que List está respaldado por una matriz interna, se asigna en un bloque contiguo. Si ese bloque asignado supera el tamaño de 85000 bytes, se asignará en el montón de objetos grandes, una generación no compactable. Dependiendo del tamaño, esto puede conducir a la fragmentación del montón, una forma leve de pérdida de memoria.
JerKimball
35
Tenga en cuenta que si está anteponiendo mucho (como está haciendo esencialmente en el último ejemplo) o eliminando la primera entrada, una lista vinculada casi siempre será significativamente más rápida, ya que no hay que buscar, mover ni copiar. Una lista requeriría mover todo hacia arriba un lugar para acomodar el nuevo elemento, haciendo que anteponer una operación O (N).
cHao
66
¿Por qué el in-loop list.AddLast(a);en los últimos dos ejemplos de LinkedList? Lo hago una vez antes del bucle, como list.AddLast(new Temp(1,1,1,1));en la penúltima LinkedList, pero (para mí) parece que estás agregando el doble de objetos Temp en los mismos bucles. (Y cuando vuelvo a verificar con una aplicación de prueba ,
efectivamente
77
Voté en contra de esta respuesta. 1) su consejo general I say never use a linkedList.es defectuoso como revela su publicación posterior. Es posible que desee editarlo. 2) ¿Qué estás cronometrando? ¿Instanciación, suma y enumeración en un solo paso? Principalmente, la creación de instancias y la enumeración no son lo que preocupa a las personas, son pasos únicos. Específicamente el tiempo de las inserciones y adiciones daría una mejor idea. 3) Lo más importante es que está agregando más de lo requerido a una lista vinculada. Esta es una comparación incorrecta. Difunde una idea equivocada sobre la lista vinculada.
nawfal
47
Lo siento, pero esta respuesta es realmente mala. Por favor NO escuche esta respuesta. Razón en pocas palabras: es completamente erróneo pensar que las implementaciones de listas respaldadas por una matriz son lo suficientemente estúpidas como para cambiar el tamaño de la matriz en cada inserción. Las listas vinculadas son naturalmente más lentas que las listas respaldadas por matriz cuando se recorren y se insertan en cualquier extremo, porque solo ellas necesitan crear nuevos objetos, mientras que las listas respaldadas por matriz usan un búfer (en ambas direcciones, obviamente). Los puntos de referencia (mal hechos) indican precisamente eso. ¡La respuesta no verifica por completo los casos en que las listas vinculadas son preferibles!
mafu
277

En la mayoría de los casos, List<T>es más útil. LinkedList<T>tendrá menos costo al agregar / eliminar elementos en el medio de la lista, mientras List<T>que solo puede agregar / eliminar de forma económica al final de la lista.

LinkedList<T>solo es más eficiente si está accediendo a datos secuenciales (ya sea hacia adelante o hacia atrás): el acceso aleatorio es relativamente costoso, ya que debe recorrer la cadena cada vez (por lo tanto, no tiene un indexador). Sin embargo, dado que a List<T>es esencialmente solo una matriz (con un contenedor), el acceso aleatorio está bien.

List<T>También ofrece una gran cantidad de métodos de apoyo - Find, ToArray, etc; sin embargo, estos también están disponibles para LinkedList<T>.NET 3.5 / C # 3.0 a través de métodos de extensión, por lo que eso es menos importante.

Marc Gravell
fuente
44
Una ventaja de List <> vs. LinkedList <> que nunca había pensado se refiere a cómo los microprocesadores implementan el almacenamiento en caché de la memoria. Aunque no lo entiendo completamente, el escritor de este artículo de blog habla mucho sobre la "localidad de referencia", lo que hace que atravesar una matriz sea mucho más rápido que atravesar una lista vinculada, al menos si la lista vinculada se ha fragmentado en la memoria . kjellkod.wordpress.com/2012/02/25/…
RenniePet
@RenniePet List se implementa con una matriz dinámica y las matrices son bloques contiguos de memoria.
Casey
2
Como List es una matriz dinámica, por eso a veces es bueno especificar la capacidad de una List en el constructor si la conoce de antemano.
Cardin Lee JH
¿Es posible que la implementación de C # de todos, array, List <T> y LinkedList <T> sea algo subóptima para un caso muy importante: necesita una lista muy grande, anexar (AddLast) y un recorrido secuencial (en una dirección) totalmente bien: no quiero cambiar el tamaño de la matriz para obtener bloques continuos (¿está garantizado para cada matriz, incluso matrices de 20 GB?), y no sé de antemano el tamaño, pero puedo adivinar de antemano un tamaño de bloque, por ejemplo, 100 MB para reservar cada vez por adelantado. Esta sería una buena implementación. ¿O es array / List similar a eso, y me perdí un punto?
Philm
1
@Philm ese es el tipo de escenario en el que escribes tu propio calce sobre la estrategia de bloqueo elegida; List<T>y T[]fallará por ser demasiado grueso (una sola losa), LinkedList<T>llorará por ser demasiado granular (losa por elemento).
Marc Gravell
212

Pensar en una lista vinculada como una lista puede ser un poco engañoso. Es más como una cadena. De hecho, en .NET, LinkedList<T>ni siquiera se implementa IList<T>. No existe un concepto real de índice en una lista vinculada, aunque parezca que sí. Ciertamente, ninguno de los métodos proporcionados en la clase acepta índices.

Las listas vinculadas pueden estar individualmente vinculadas o doblemente vinculadas. Esto se refiere a si cada elemento de la cadena tiene un enlace solo con el siguiente (vinculado individualmente) o con los elementos anteriores / siguientes (doblemente vinculados). LinkedList<T>está doblemente vinculado

Internamente, List<T>está respaldado por una matriz. Esto proporciona una representación muy compacta en la memoria. Por el contrario, LinkedList<T>implica memoria adicional para almacenar los enlaces bidireccionales entre elementos sucesivos. Por lo tanto, la huella de memoria de a LinkedList<T>generalmente será mayor que para List<T>(con la advertencia de que List<T>puede tener elementos de matriz internos no utilizados para mejorar el rendimiento durante las operaciones de adición).

También tienen diferentes características de rendimiento:

Adjuntar

  • LinkedList<T>.AddLast(item) tiempo constante
  • List<T>.Add(item) tiempo constante amortizado, peor caso lineal

Anteponer

  • LinkedList<T>.AddFirst(item) tiempo constante
  • List<T>.Insert(0, item) tiempo lineal

Inserción

  • LinkedList<T>.AddBefore(node, item) tiempo constante
  • LinkedList<T>.AddAfter(node, item) tiempo constante
  • List<T>.Insert(index, item) tiempo lineal

Eliminación

  • LinkedList<T>.Remove(item) tiempo lineal
  • LinkedList<T>.Remove(node) tiempo constante
  • List<T>.Remove(item) tiempo lineal
  • List<T>.RemoveAt(index) tiempo lineal

Contar

  • LinkedList<T>.Count tiempo constante
  • List<T>.Count tiempo constante

Contiene

  • LinkedList<T>.Contains(item) tiempo lineal
  • List<T>.Contains(item) tiempo lineal

Claro

  • LinkedList<T>.Clear() tiempo lineal
  • List<T>.Clear() tiempo lineal

Como puede ver, son en su mayoría equivalentes. En la práctica, la API de LinkedList<T>es más engorrosa de usar y los detalles de sus necesidades internas se derraman en su código.

Sin embargo, si necesita hacer muchas inserciones / eliminaciones desde una lista, ofrece tiempo constante. List<T>ofrece tiempo lineal, ya que los elementos adicionales en la lista se deben barajar después de la inserción / eliminación.

Drew Noakes
fuente
2
¿Es constante la cuenta enlazada? Pensé que sería lineal?
Iain Ballard el
10
@Iain, el recuento se almacena en caché en ambas clases de lista.
Drew Noakes el
3
Usted escribió que "Lista <T>. Agregar tiempo logarítmico (elemento)", sin embargo, de hecho es "Constante" si la capacidad de la lista puede almacenar el nuevo elemento, y "Lineal" si la lista no tiene suficiente espacio y para ser reasignado
aStranger
@aStranger, por supuesto que tienes razón. No estoy seguro de lo que estaba pensando en lo anterior, tal vez que el tiempo normal amortizado del caso es logarítmico, que no lo es. De hecho, el tiempo amortizado es constante. No me metí en el mejor / peor de los casos de las operaciones, buscando una comparación simple. Sin embargo, creo que la operación de agregar es lo suficientemente significativa como para proporcionar este detalle. Editará la respuesta. Gracias.
Drew Noakes
1
@Philm, posiblemente deberías comenzar una nueva pregunta, y no dices cómo vas a usar esta estructura de datos una vez construida, pero si estás hablando de un millón de filas, te podría gustar algún tipo de híbrido (lista enlazada de trozos de matriz o similares) para reducir la fragmentación del montón, reducir la sobrecarga de memoria y evitar un solo objeto enorme en el LOH.
Drew Noakes
118

Las listas vinculadas proporcionan una inserción o eliminación muy rápida de un miembro de la lista. Cada miembro de una lista vinculada contiene un puntero al siguiente miembro de la lista para insertar un miembro en la posición i:

  • actualice el puntero en el miembro i-1 para que apunte al nuevo miembro
  • establecer el puntero en el nuevo miembro para que apunte al miembro i

La desventaja de una lista vinculada es que el acceso aleatorio no es posible. Acceder a un miembro requiere recorrer la lista hasta que se encuentre el miembro deseado.

b3.
fuente
66
Agregaría que las listas vinculadas tienen una sobrecarga por elemento almacenado implícitamente a través de LinkedListNode que hace referencia al nodo anterior y al siguiente. La recompensa de eso es un bloque contiguo de memoria que no se requiere para almacenar la lista, a diferencia de una lista basada en una matriz.
paulecoyote
3
¿No es generalmente un bloque de memoria contiguo?
Jonathan Allen
77
Sí, se prefiere un bloque contiguo para el rendimiento de acceso aleatorio y el consumo de memoria, pero para las colecciones que necesitan cambiar el tamaño regularmente, una estructura como una matriz generalmente debe copiarse en una nueva ubicación, mientras que una lista vinculada solo necesita administrar la memoria para nodos recién insertados / eliminados.
jpierson
66
Si alguna vez ha tenido que trabajar con matrices o listas muy grandes (una lista simplemente envuelve una matriz), comenzará a tener problemas de memoria a pesar de que parece que hay mucha memoria disponible en su máquina. La lista utiliza una estrategia de duplicación cuando asigna un nuevo espacio en su matriz subyacente. Por lo tanto, una matriz elemnt 1000000 que está llena se copiará en una nueva matriz con elementos 2000000. Este nuevo conjunto debe crearse en un espacio de memoria contiguo que sea lo suficientemente grande como para contenerlo.
Andrew
1
Tuve un caso específico en el que todo lo que hice fue agregar y quitar, y hacer bucles uno por uno ... aquí la lista vinculada era muy superior a la lista normal ...
Peter
26

Mi respuesta anterior no fue lo suficientemente precisa. Como realmente fue horrible: D Pero ahora puedo publicar una respuesta mucho más útil y correcta.


Hice algunas pruebas adicionales. Puede encontrar su fuente en el siguiente enlace y volver a verificarlo en su entorno: https://github.com/ukushu/DataStructuresTestsAndOther.git

Resultados cortos:

  • Matriz necesita usar:

    • Tan a menudo como sea posible. Es rápido y toma el rango de RAM más pequeño para la misma cantidad de información.
    • Si sabe el recuento exacto de células necesarias
    • Si los datos se guardaron en una matriz <85000 b (85000/32 = 2656 elementos para datos enteros)
    • Si es necesario, alta velocidad de acceso aleatorio
  • Lista necesita usar:

    • Si es necesario agregar celdas al final de la lista (a menudo)
    • Si es necesario agregar celdas al principio / medio de la lista (NO A MENUDO)
    • Si los datos se guardaron en una matriz <85000 b (85000/32 = 2656 elementos para datos enteros)
    • Si es necesario, alta velocidad de acceso aleatorio
  • LinkedList necesita usar:

    • Si es necesario agregar celdas al principio / medio / final de la lista (a menudo)
    • Si es necesario solo acceso secuencial (adelante / atrás)
    • Si necesita guardar artículos GRANDES, pero el recuento de artículos es bajo.
    • Mejor no lo use para una gran cantidad de elementos, ya que usa memoria adicional para los enlaces.

Más detalles:

введите сюда описание изображения Interesante saber:

  1. LinkedList<T>internamente no es una Lista en .NET. Incluso no se implementa IList<T>. Y es por eso que hay índices y métodos ausentes relacionados con los índices.

  2. LinkedList<T>es una colección basada en puntero de nodo. En .NET está en implementación doblemente vinculada. Esto significa que los elementos anteriores / siguientes tienen un enlace al elemento actual. Y los datos están fragmentados: se pueden ubicar diferentes objetos de lista en diferentes lugares de RAM. También habrá más memoria utilizada para LinkedList<T>que para List<T>o Array.

  3. List<T>en .Net es la alternativa de Java de ArrayList<T>. Esto significa que este es un contenedor de matriz. Por lo tanto, se asigna en la memoria como un bloque contiguo de datos. Si el tamaño de datos asignado supera los 85000 bytes, se moverá a Montón de objetos grandes. Dependiendo del tamaño, esto puede conducir a la fragmentación del montón (una forma leve de pérdida de memoria). Pero al mismo tiempo si el tamaño es <85000 bytes, esto proporciona una representación muy compacta y de acceso rápido en la memoria.

  4. Se prefiere un bloque contiguo único para el rendimiento de acceso aleatorio y el consumo de memoria, pero para las colecciones que necesitan cambiar regularmente el tamaño, una estructura como una matriz generalmente debe copiarse en una nueva ubicación, mientras que una lista vinculada solo necesita administrar la memoria para el recién insertado / nodos eliminados.

Andrés
fuente
1
Pregunta: Con "datos guardados en la matriz <o> 85,000 byte" quiere decir datos por matriz / lista ELEMENTO ¿verdad? Podría entenderse que te refieres al tamaño de datos de toda la matriz ..
Philm
Elementos de matriz ubicados secuencialmente en la memoria. Entonces por arreglo. Sé sobre el error en la tabla, luego lo arreglaré :) (espero ...)
Andrew
Con las listas siendo lentas en las inserciones, si una lista tiene muchos cambios (muchas inserciones / eliminaciones), la memoria está ocupada por el espacio eliminado y, de ser así, ¿eso hace que las inserciones "re" sean más rápidas?
Rob
18

La diferencia entre List y LinkedList radica en su implementación subyacente. La lista es una colección basada en matriz (ArrayList). LinkedList es una colección basada en puntero de nodo (LinkedListNode). En el uso de nivel API, ambos son prácticamente iguales ya que ambos implementan el mismo conjunto de interfaces como ICollection, IEnumerable, etc.

La diferencia clave viene cuando el rendimiento es importante. Por ejemplo, si está implementando la lista que tiene una operación pesada "INSERTAR", LinkedList supera a List. Dado que LinkedList puede hacerlo en tiempo O (1), pero List puede necesitar expandir el tamaño de la matriz subyacente. Para obtener más información / detalles, es posible que desee leer sobre la diferencia algorítmica entre LinkedList y las estructuras de datos de matriz. http://en.wikipedia.org/wiki/Linked_list and Array

Espero que esto ayude,

usuario23117
fuente
44
La lista <T> se basa en una matriz (T []), no en una matriz de listas. Volver a insertar: el cambio de tamaño de la matriz no es el problema (el algoritmo de duplicación significa que la mayoría de las veces no tiene que hacer esto): el problema es que primero debe copiar en bloque todos los datos existentes, lo que requiere un poco hora.
Marc Gravell
2
@Marc, el 'algoritmo de duplicación "solo lo convierte en O (logN), pero aún es peor que O (1)
Ilya Ryzhenkov
2
Mi punto era que no es el cambio de tamaño lo que causa el dolor, es el blit. Entonces, en el peor de los casos, si estamos agregando el primer elemento (cero) cada vez, entonces el blit tiene que mover todo cada vez.
Marc Gravell
@IlyaRyzhenkov: está pensando en el caso en el Addque siempre está al final de la matriz existente. Listes "suficientemente bueno" en eso, incluso si no es O (1). El problema grave se produce si necesita muchos Addcorreos electrónicos que no están al final. Marc señala que la necesidad de mover datos existentes cada vez que inserte (no solo cuando se necesita cambiar el tamaño) es un costo de rendimiento más sustancial List.
ToolmakerSteve
El problema es que las notaciones teóricas de Big O no cuentan toda la historia. En ciencias de la computación eso es lo único que le importa a alguien, pero hay mucho más de lo que preocuparse en el mundo real.
MattE
11

La principal ventaja de las listas vinculadas sobre las matrices es que los enlaces nos brindan la capacidad de reorganizar los elementos de manera eficiente. Sedgewick, pág. 91 91

Dr. Alrawi
fuente
1
OMI, esta debería ser la respuesta. LinkedList se usa cuando un pedido garantizado es importante.
RBaarda
1
@RBaarda: No estoy de acuerdo. Depende del nivel del que estemos hablando. El nivel algorítmico es diferente al nivel de implementación de la máquina. Para tener en cuenta la velocidad, también necesita este último. Como se señaló, las matrices se implementan por ser "un trozo" de memoria, lo que es una restricción, ya que esto puede conducir al cambio de tamaño y la reorganización de la memoria, especialmente con matrices muy grandes. Después de pensar un momento, una estructura de datos propia especial, una lista vinculada de matrices sería una idea para dar un mejor control sobre la velocidad de llenado lineal y acceder a estructuras de datos muy grandes.
Philm
1
@Philm: he votado tu comentario, pero me gustaría señalar que estás describiendo un requisito diferente. Lo que dice la respuesta es que la lista vinculada tiene una ventaja de rendimiento para los algoritmos que implican una gran cantidad de reorganización de elementos. Dado eso, interpreto que el comentario de RBaarda se refiere a la necesidad de agregar / eliminar elementos mientras se mantiene continuamente un orden dado (criterios de clasificación). Así que no solo "relleno lineal". Dado esto, List pierde, porque los índices son inútiles (cambian cada vez que agrega un elemento en cualquier lugar que no sea el final).
ToolmakerSteve
4

Una circunstancia común para usar LinkedList es así:

Suponga que desea eliminar muchas cadenas determinadas de una lista de cadenas con un tamaño grande, digamos 100,000. Las cadenas para eliminar se pueden buscar en HashSet dic, y se cree que la lista de cadenas contiene entre 30,000 y 60,000 cadenas para eliminar.

Entonces, ¿cuál es el mejor tipo de lista para almacenar las 100.000 cadenas? La respuesta es LinkedList. Si se almacenan en una ArrayList, la iteración sobre ella y la eliminación de cadenas coincidentes requeriría miles de millones de operaciones, mientras que solo se necesitan alrededor de 100,000 operaciones mediante el uso de un iterador y el método remove ().

LinkedList<String> strings = readStrings();
HashSet<String> dic = readDic();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()){
    String string = iterator.next();
    if (dic.contains(string))
    iterator.remove();
}
Tom
fuente
66
Simplemente puede usar RemoveAllpara eliminar los elementos de a Listsin mover muchos elementos, o usar Wherede LINQ para crear una segunda lista. LinkedListSin embargo, el uso de un aquí termina consumiendo dramáticamente más memoria que otros tipos de colecciones y la pérdida de la localidad de memoria significa que será notablemente más lento para iterar, lo que lo hace un poco peor que a List.
Servicio
@Servy, ten en cuenta que la respuesta de @ Tom usa Java. No estoy seguro de si hay un RemoveAllequivalente en Java.
Arturo Torres Sánchez
3
@ ArturoTorresSánchez Bueno, la pregunta dice específicamente que se trata de .NET, por lo que eso hace que la respuesta sea mucho menos apropiada.
Servicio
@Servy, entonces deberías haber mencionado eso desde el principio.
Arturo Torres Sánchez
Si RemoveAllno está disponible para List, podría hacer un algoritmo de "compactación", que se vería como el bucle de Tom, pero con dos índices y la necesidad de mover elementos para mantenerse uno a la vez en la matriz interna de la lista. La eficiencia es O (n), lo mismo que para el algoritmo de Tom LinkedList. En ambas versiones, domina el tiempo para calcular la clave HashSet para las cadenas. Este no es un buen ejemplo de cuándo usarlo LinkedList.
ToolmakerSteve
2

Cuando necesite acceso indexado incorporado, clasificación (y después de esta búsqueda binaria) y método "ToArray ()", debe usar List.

Michael Damatov
fuente
2

Esencialmente, un List<>en .NET es un contenedor sobre una matriz . A LinkedList<> es una lista vinculada . Entonces, la pregunta se reduce a cuál es la diferencia entre una matriz y una lista vinculada, y cuándo debe usarse una matriz en lugar de una lista vinculada. Probablemente los dos factores más importantes en su decisión de cuál usar se reducirían a:

  • Las listas vinculadas tienen un rendimiento de inserción / eliminación mucho mejor, siempre que las inserciones / eliminaciones no estén en el último elemento de la colección. Esto se debe a que una matriz debe desplazar todos los elementos restantes que vienen después del punto de inserción / eliminación. Sin embargo, si la inserción / eliminación se encuentra al final de la lista, este cambio no es necesario (aunque es posible que se deba cambiar el tamaño de la matriz, si se excede su capacidad).
  • Las matrices tienen capacidades de acceso mucho mejores. Las matrices se pueden indexar directamente (en tiempo constante). Las listas vinculadas deben atravesarse (tiempo lineal).
iliketocode
fuente
1

Esto está adaptado de la respuesta aceptada de Tono Nam corrigiendo algunas mediciones incorrectas.

La prueba:

static void Main()
{
    LinkedListPerformance.AddFirst_List(); // 12028 ms
    LinkedListPerformance.AddFirst_LinkedList(); // 33 ms

    LinkedListPerformance.AddLast_List(); // 33 ms
    LinkedListPerformance.AddLast_LinkedList(); // 32 ms

    LinkedListPerformance.Enumerate_List(); // 1.08 ms
    LinkedListPerformance.Enumerate_LinkedList(); // 3.4 ms

    //I tried below as fun exercise - not very meaningful, see code
    //sort of equivalent to insertion when having the reference to middle node

    LinkedListPerformance.AddMiddle_List(); // 5724 ms
    LinkedListPerformance.AddMiddle_LinkedList1(); // 36 ms
    LinkedListPerformance.AddMiddle_LinkedList2(); // 32 ms
    LinkedListPerformance.AddMiddle_LinkedList3(); // 454 ms

    Environment.Exit(-1);
}

Y el codigo:

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace stackoverflow
{
    static class LinkedListPerformance
    {
        class Temp
        {
            public decimal A, B, C, D;

            public Temp(decimal a, decimal b, decimal c, decimal d)
            {
                A = a; B = b; C = c; D = d;
            }
        }



        static readonly int start = 0;
        static readonly int end = 123456;
        static readonly IEnumerable<Temp> query = Enumerable.Range(start, end - start).Select(temp);

        static Temp temp(int i)
        {
            return new Temp(i, i, i, i);
        }

        static void StopAndPrint(this Stopwatch watch)
        {
            watch.Stop();
            Console.WriteLine(watch.Elapsed.TotalMilliseconds);
        }

        public static void AddFirst_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(0, temp(i));

            watch.StopAndPrint();
        }

        public static void AddFirst_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddFirst(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Add(temp(i));

            watch.StopAndPrint();
        }

        public static void AddLast_LinkedList()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (int i = start; i < end; i++)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        public static void Enumerate_List()
        {
            var list = new List<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        public static void Enumerate_LinkedList()
        {
            var list = new LinkedList<Temp>(query);
            var watch = Stopwatch.StartNew();

            foreach (var item in list)
            {

            }

            watch.StopAndPrint();
        }

        //for the fun of it, I tried to time inserting to the middle of 
        //linked list - this is by no means a realistic scenario! or may be 
        //these make sense if you assume you have the reference to middle node

        //insertion to the middle of list
        public static void AddMiddle_List()
        {
            var list = new List<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
                list.Insert(list.Count / 2, temp(i));

            watch.StopAndPrint();
        }

        //insertion in linked list in such a fashion that 
        //it has the same effect as inserting into the middle of list
        public static void AddMiddle_LinkedList1()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            LinkedListNode<Temp> evenNode = null, oddNode = null;
            for (int i = start; i < end; i++)
            {
                if (list.Count == 0)
                    oddNode = evenNode = list.AddLast(temp(i));
                else
                    if (list.Count % 2 == 1)
                        oddNode = list.AddBefore(evenNode, temp(i));
                    else
                        evenNode = list.AddAfter(oddNode, temp(i));
            }

            watch.StopAndPrint();
        }

        //another hacky way
        public static void AddMiddle_LinkedList2()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start + 1; i < end; i += 2)
                list.AddLast(temp(i));
            for (int i = end - 2; i >= 0; i -= 2)
                list.AddLast(temp(i));

            watch.StopAndPrint();
        }

        //OP's original more sensible approach, but I tried to filter out
        //the intermediate iteration cost in finding the middle node.
        public static void AddMiddle_LinkedList3()
        {
            var list = new LinkedList<Temp>();
            var watch = Stopwatch.StartNew();

            for (var i = start; i < end; i++)
            {
                if (list.Count == 0)
                    list.AddLast(temp(i));
                else
                {
                    watch.Stop();
                    var curNode = list.First;
                    for (var j = 0; j < list.Count / 2; j++)
                        curNode = curNode.Next;
                    watch.Start();

                    list.AddBefore(curNode, temp(i));
                }
            }

            watch.StopAndPrint();
        }
    }
}

Puede ver que los resultados están de acuerdo con el rendimiento teórico que otros han documentado aquí. Muy claro: LinkedList<T>gana mucho tiempo en caso de inserciones. No he probado la eliminación del medio de la lista, pero el resultado debería ser el mismo. Por supuesto, List<T>tiene otras áreas donde funciona mucho mejor como el acceso aleatorio O (1).

nawfal
fuente
0

Usar LinkedList<>cuando

  1. No sabes cuántos objetos entran por la puerta de inundación. Por ejemplo, Token Stream.
  2. Cuando SOLO quería eliminar \ insertar en los extremos.

Para todo lo demás, es mejor usarlo List<>.

Antony Thomas
fuente
66
No veo por qué el punto 2 tiene sentido. Las listas vinculadas son excelentes cuando realiza muchas inserciones / eliminaciones en toda la lista.
Drew Noakes
Debido al hecho de que LinkedLists no están basadas en índices, realmente tiene que escanear toda la lista en busca de inserción o eliminación que incurre en una penalización O (n). List <> por otro lado sufre de cambio de tamaño de matriz, pero aún así, IMO, es una mejor opción en comparación con LinkedLists.
Antony Thomas
1
No tiene que escanear la lista en busca de inserciones / eliminaciones si realiza un seguimiento de los LinkedListNode<T>objetos en su código. Si puede hacer eso, entonces es mucho mejor que usar List<T>, especialmente para listas muy largas donde las inserciones / extracciones son frecuentes.
Drew Noakes
¿Te refieres a través de una tabla hash? Si ese es el caso, esa sería la compensación típica de espacio / tiempo que cada programador de computadoras debería elegir en función del dominio del problema :) Pero sí, eso lo haría más rápido.
Antony Thomas
1
@AntonyThomas: No, quiere decir que pasa referencias a nodos en lugar de pasar referencias a elementos . Si todo lo que tiene es un elemento , tanto List como LinkedList tienen un mal rendimiento, ya que debe buscar. Si piensa "pero con la Lista, puedo pasar un índice": eso solo es válido cuando nunca inserta un nuevo elemento en el medio de la Lista. LinkedList no tiene esta limitación, si mantiene un nodo (y lo usa node.Valuecuando quiere el elemento original). Entonces reescribe el algoritmo para trabajar con nodos, no con valores sin formato.
ToolmakerSteve
0

Estoy de acuerdo con la mayoría de los puntos mencionados anteriormente. Y también estoy de acuerdo en que List parece una opción más obvia en la mayoría de los casos.

Pero, solo quiero agregar que hay muchas instancias en las que LinkedList es una opción mucho mejor que List para una mejor eficiencia.

  1. Suponga que está atravesando los elementos y desea realizar muchas inserciones / eliminaciones; LinkedList lo hace en tiempo lineal O (n), mientras que List lo hace en tiempo cuadrático O (n ^ 2).
  2. Supongamos que desea acceder a objetos más grandes una y otra vez, LinkedList se vuelve mucho más útil.
  3. Deque () y queue () se implementan mejor usando LinkedList.
  4. Aumentar el tamaño de LinkedList es mucho más fácil y mejor una vez que se trata de muchos y más grandes objetos.

Espero que alguien encuentre útiles estos comentarios.

Abhishek
fuente
Tenga en cuenta que este consejo es para .NET, no para Java. En la implementación de la lista vinculada de Java no tiene el concepto de un "nodo actual", por lo que debe recorrer la lista para cada inserción.
Jonathan Allen
Esta respuesta es solo parcialmente correcta: 2) si los elementos son grandes, haga que el elemento escriba una clase, no una estructura, de modo que la lista simplemente contenga una referencia. Entonces el tamaño del elemento se vuelve irrelevante. 3) Deque y cola se pueden hacer de manera eficiente en una Lista si utiliza Lista como un "búfer circular", en lugar de insertar o eliminar al inicio. Deque de Stephen Cleary . 4) parcialmente cierto: cuando muchos objetos, pro de LL no necesita una gran memoria contigua; Lo malo es la memoria extra para los punteros de nodo.
ToolmakerSteve
-2

Tantas respuestas promedio aquí ...

Algunas implementaciones de listas vinculadas utilizan bloques subyacentes de nodos preasignados. Si no lo hacen, el tiempo constante / tiempo lineal es menos relevante ya que el rendimiento de la memoria será deficiente y el rendimiento de la memoria caché será aún peor.

Use listas vinculadas cuando

1) Desea seguridad del hilo. Puedes construir mejores algos seguros para hilos. Los costos de bloqueo dominarán una lista de estilos concurrentes.

2) Si tiene una cola grande como estructuras y desea eliminar o agregar en cualquier lugar excepto el final todo el tiempo. > Existen 100K listas pero no son tan comunes.

usuario1496062
fuente
3
Esta pregunta fue sobre dos implementaciones de C #, no listas enlazadas en general.
Jonathan Allen el
Lo mismo en todos los idiomas
user1496062
-2

Hice una pregunta similar relacionada con el rendimiento de la colección LinkedList , y descubrí que el implemento C # de Deque de Steven Cleary era una solución. A diferencia de la colección Queue, Deque permite mover elementos hacia adelante y hacia atrás. Es similar a la lista vinculada, pero con un rendimiento mejorado.

Adam Cox
fuente
1
Re su declaración que Dequees "similar a la lista vinculada, pero con un rendimiento mejorado" . Por favor calificar esa declaración: Dequees un mejor rendimiento que LinkedList, para su código específico . Siguiendo su enlace, veo que dos días después usted aprendió de Ivan Stoev que esto no era una ineficiencia de LinkedList, sino una ineficiencia en su código. (E incluso si hubiera sido una ineficiencia de LinkedList, eso no justificaría una declaración general de que Deque es más eficiente; solo en casos específicos.)
ToolmakerSteve