Quando devo usar uma lista versus uma lista vinculada

393

Quando é melhor usar uma lista versus uma lista vinculada ?

Jonathan Allen
fonte
3
Java q , não deve ser muito diferente.
Nawfal #
11
@ Jonathan-Allen, Por favor, considere alterar a resposta aceita. O atual é impreciso e extremamente enganoso.
Xpleria 9/08

Respostas:

107

Editar

Por favor, leia os comentários para esta resposta. As pessoas afirmam que eu não fiz testes adequados. Concordo que isso não deve ser uma resposta aceita. Enquanto aprendia, fiz alguns testes e tive vontade de compartilhá-los.

Resposta original ...

Encontrei resultados interessantes:

// 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;

Mesmo se você acessar apenas os dados essencialmente, é muito mais lento !! Eu digo nunca use um LinkedList.




Aqui está outra comparação executando várias inserções (planejamos inserir um item no meio da 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 com referência do local onde inserir (0,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;

Portanto, somente se você planeja inserir vários itens e também tiver em algum lugar a referência de onde planeja inserir o item, use uma lista vinculada. Só porque você precisa inserir muitos itens, não o torna mais rápido, porque pesquisar no local em que você deseja inserir leva tempo.

Tono Nam
fonte
99
Há um benefício no LinkedList over List (isso é específico do .net): como a Lista é apoiada por uma matriz interna, ela é alocada em um bloco contíguo. Se esse bloco alocado exceder 85000 bytes de tamanho, ele será alocado no Large Object Heap, uma geração não compactável. Dependendo do tamanho, isso pode levar à fragmentação da pilha, uma forma moderada de vazamento de memória.
JerKimball
35
Observe que, se você estiver anexando muito (como você faz essencialmente no último exemplo) ou excluindo a primeira entrada, uma lista vinculada quase sempre será significativamente mais rápida, pois não há pesquisa ou movimentação / cópia. Uma lista exigiria mover tudo para cima de um ponto para acomodar o novo item, fazendo o pré-anexo de uma operação O (N).
cHao 20/09/12
6
Por que o loop list.AddLast(a);nos dois últimos exemplos de LinkedList? Eu faço isso uma vez antes do loop, como list.AddLast(new Temp(1,1,1,1));no próximo ao último LinkedList, mas parece (para mim) que você está adicionando o dobro de objetos Temp nos próprios loops. (E quando eu me checar com um aplicativo de teste , com certeza, o dobro na LinkedList.)
ruffin
7
Eu diminuí a votação para esta resposta. 1) seu conselho geral I say never use a linkedList.é falho, como revela seu post posterior. Você pode querer editá-lo. 2) O que você está cronometrando? Instanciação, adição e enumeração em uma única etapa? Principalmente, instanciação e enumeração não são o que preocupam as pessoas, essas são etapas únicas. Cronometrar especificamente as inserções e adições daria uma idéia melhor. 3) Mais importante, você está adicionando mais do que o necessário a uma lista vinculada. Esta é uma comparação errada. Espalha a ideia errada sobre a lista vinculada.
Nawfal
47
Desculpe, mas esta resposta é muito ruim. Por favor, NÃO ouça esta resposta. Razão em poucas palavras: é completamente incorreto pensar que as implementações de lista apoiadas em matriz são estúpidas o suficiente para redimensionar a matriz em cada inserção. As listas vinculadas são naturalmente mais lentas do que as listas de backups de matriz ao atravessar e ao inserir nas duas extremidades, porque somente elas precisam criar novos objetos, enquanto as listas apoiadas em matriz usam um buffer (em ambas as direções, obviamente). Os benchmarks (mal feitos) indicam precisamente isso. A resposta falha completamente em verificar os casos em que as listas vinculadas são preferíveis!
Mafu #
277

Na maioria dos casos, List<T>é mais útil. LinkedList<T>terá menos custo ao adicionar / remover itens no meio da lista, ao passo que List<T>apenas poderá adicionar / remover mais barato no final da lista.

LinkedList<T>é o mais eficiente se você estiver acessando dados seqüenciais (para frente ou para trás) - o acesso aleatório é relativamente caro, pois deve percorrer a cadeia a cada vez (daí o motivo de não ter um indexador). No entanto, como a List<T>é essencialmente apenas um acesso aleatório de matriz (com um invólucro), tudo bem.

List<T>também oferece uma série de métodos de apoio - Find, ToArray, etc; no entanto, eles também estão disponíveis LinkedList<T>no .NET 3.5 / C # 3.0 por meio de métodos de extensão - portanto, isso é menos importante.

Marc Gravell
fonte
4
Uma vantagem da List <> vs. LinkedList <> que eu nunca pensei sobre como os microprocessadores implementam o cache da memória. Embora eu não entenda completamente, o autor deste artigo do blog fala muito sobre "localidade de referência", o que torna a travessia de uma matriz muito mais rápida do que a travessia de uma lista vinculada, pelo menos se a lista vinculada tiver se fragmentado na memória . kjellkod.wordpress.com/2012/02/25/...
RenniePet
A @RenniePet List é implementada com uma matriz dinâmica e as matrizes são blocos contíguos de memória.
Casey #
2
Como List é uma matriz dinâmica, é por isso que às vezes é bom especificar a capacidade de uma Lista no construtor, se você a conhecer de antemão.
Cardin Lee JH
É possível que a implementação C # de all, array, List <T> e LinkedList <T> seja um pouco abaixo do ideal para um caso muito importante: Você precisa de uma lista muito grande, anexar (AddLast) e passagem sequencial (em uma direção). totalmente bom: não quero que o redimensionamento de matriz obtenha blocos contínuos (é garantido para cada matriz, mesmo matrizes de 20 GB?), e não conheço antecipadamente o tamanho, mas posso adivinhar com antecedência o tamanho de um bloco, por exemplo, 100 MB para reservar sempre com antecedência. Essa seria uma boa implementação. Ou array / List é semelhante a isso, e eu perdi um ponto?
Philm
11
@Film, esse é o tipo de cenário em que você escreve seu próprio calço sobre a estratégia de blocos escolhida; List<T>e T[]falhará por ser muito robusto (tudo uma laje), LinkedList<T>lamentará por ser muito granular (laje por elemento).
Marc Gravell
212

Pensar em uma lista vinculada como uma lista pode ser um pouco enganador. É mais como uma corrente. De fato, no .NET, LinkedList<T>nem sequer implementa IList<T>. Não existe um conceito real de índice em uma lista vinculada, mesmo que pareça existir. Certamente, nenhum dos métodos fornecidos na classe aceita índices.

As listas vinculadas podem ser vinculadas individualmente ou duplamente vinculadas. Isso se refere ao fato de cada elemento da cadeia ter um link apenas para o próximo (vinculado individualmente) ou para os elementos anteriores / próximos (vinculados duplamente). LinkedList<T>está duplamente ligado.

Internamente, List<T>é apoiado por uma matriz. Isso fornece uma representação muito compacta na memória. Por outro lado, LinkedList<T>envolve memória adicional para armazenar os links bidirecionais entre elementos sucessivos. Portanto, o espaço de memória de a LinkedList<T>geralmente será maior do que para List<T>(com a ressalva de que List<T>pode haver elementos de matriz internos não utilizados para melhorar o desempenho durante operações de acréscimo).

Eles também têm características de desempenho diferentes:

Acrescentar

  • LinkedList<T>.AddLast(item) tempo constante
  • List<T>.Add(item) tempo constante amortizado, pior caso linear

Anexar

  • LinkedList<T>.AddFirst(item) tempo constante
  • List<T>.Insert(0, item) tempo linear

Inserção

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

Remoção

  • LinkedList<T>.Remove(item) tempo linear
  • LinkedList<T>.Remove(node) tempo constante
  • List<T>.Remove(item) tempo linear
  • List<T>.RemoveAt(index) tempo linear

Contagem

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

Contém

  • LinkedList<T>.Contains(item) tempo linear
  • List<T>.Contains(item) tempo linear

Claro

  • LinkedList<T>.Clear() tempo linear
  • List<T>.Clear() tempo linear

Como você pode ver, eles são basicamente equivalentes. Na prática, a API de LinkedList<T>é mais complicada de usar e detalhes de suas necessidades internas se espalham pelo seu código.

No entanto, se você precisar fazer muitas inserções / remoções de uma lista, ele oferece tempo constante. List<T>oferece tempo linear, pois itens extras na lista devem ser embaralhados após a inserção / remoção.

Drew Noakes
fonte
2
A contagem de lista vinculada é constante? Eu pensei que seria linear?
Iain Ballard
10
@Iain, a contagem é armazenada em cache nas duas classes da lista.
de Drew Noakes
3
Você escreveu que "Lista <T> .Add (item) tempo logarítmico"; no entanto, é de fato "Constante" se a capacidade da lista puder armazenar o novo item e "Linear" se a lista não tiver espaço suficiente e novas informações. para ser realocado.
aStranger
@ aStranger, é claro que você está certo. Não tenho certeza do que eu estava pensando acima - talvez o tempo normal amortizado seja logarítmico, o que não é. De fato, o tempo amortizado é constante. Não entrei no melhor / pior caso das operações, buscando uma comparação simples. Eu acho que a operação de adição é significativa o suficiente para fornecer esses detalhes, no entanto. Editará a resposta. Obrigado.
Desenhou Noakes
11
@ Philm, você deve iniciar uma nova pergunta e não diz como vai usar essa estrutura de dados depois de criada, mas se estiver falando um milhão de linhas, poderá gostar de algum tipo de híbrido (lista vinculada de pedaços de matriz ou similares) para reduzir a fragmentação de heap, reduzir a sobrecarga de memória e evitar um único objeto enorme no LOH.
Desenhou Noakes
118

As listas vinculadas fornecem inserção ou exclusão muito rápidas de um membro da lista. Cada membro de uma lista vinculada contém um ponteiro para o próximo membro da lista, para inserir um membro na posição i:

  • atualize o ponteiro no membro i-1 para apontar para o novo membro
  • defina o ponteiro no novo membro para apontar para o membro i

A desvantagem de uma lista vinculada é que o acesso aleatório não é possível. Para acessar um membro, é necessário percorrer a lista até que o membro desejado seja encontrado.

b3
fonte
6
Eu acrescentaria que as listas vinculadas têm uma sobrecarga por item armazenado acima, via LinkedListNode, que referencia o nó anterior e o próximo. O resultado disso é um bloco contíguo de memória não é necessário para armazenar a lista, ao contrário de uma lista baseada em matriz.
22139 paulecoyote
3
Um bloco de memória contíguo não é geralmente preferido?
Jonathan Allen
7
Sim, um bloco contíguo é preferido para desempenho de acesso aleatório e consumo de memória, mas para coleções que precisam mudar de tamanho regularmente, uma estrutura como uma Matriz geralmente precisa ser copiada para um novo local, enquanto uma lista vinculada precisa apenas gerenciar a memória para o nós recém-inseridos / excluídos.
jpierson
6
Se você já teve que trabalhar com matrizes ou listas muito grandes (uma lista apenas agrupa uma matriz), você começará a encontrar problemas de memória, mesmo que pareça haver bastante memória disponível em sua máquina. A lista usa uma estratégia de duplicação quando aloca novo espaço em sua matriz subjacente. Portanto, uma matriz elemnt 1000000 cheia será copiada para uma nova matriz com 2000000 elementos. Essa nova matriz precisa ser criada em um espaço de memória contíguo que seja grande o suficiente para mantê-lo.
Andrew
11
Eu tive um caso específico em que tudo o que fiz foi adicionar e remover e fazer um loop um por um ... aqui a lista vinculada era muito superior à lista normal. #
Peter Peter
26

Minha resposta anterior não foi suficientemente precisa. Como realmente foi horrível: D Mas agora posso postar respostas muito mais úteis e corretas.


Eu fiz alguns testes adicionais. Você pode encontrar a fonte no seguinte link e verificar novamente em seu ambiente: https://github.com/ukushu/DataStructuresTestsAndOther.git

Resultados curtos:

  • A matriz precisa usar:

    • Tão frequentemente quanto possível. É rápido e utiliza o menor intervalo de RAM para obter informações sobre a mesma quantidade.
    • Se você souber a contagem exata de células necessárias
    • Se os dados salvos na matriz <85000 b (85000/32 = 2656 elementos para dados inteiros)
    • Se necessário, alta velocidade de acesso aleatório
  • A lista precisa usar:

    • Se necessário, adicione células ao final da lista (geralmente)
    • Se necessário, adicione células no começo / meio da lista (NÃO É MUITO)
    • Se os dados salvos na matriz <85000 b (85000/32 = 2656 elementos para dados inteiros)
    • Se necessário, alta velocidade de acesso aleatório
  • O LinkedList precisa usar:

    • Se necessário, adicione células no começo / meio / fim da lista (frequentemente)
    • Se necessário, apenas acesso sequencial (avançar / retroceder)
    • Se você precisar salvar itens GRANDES, mas a contagem de itens é baixa.
    • Melhor não usar para uma grande quantidade de itens, pois é usar memória adicional para links.

Mais detalhes:

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

  1. LinkedList<T>internamente não é uma lista no .NET. É mesmo não implementa IList<T>. E é por isso que existem índices e métodos ausentes relacionados a índices.

  2. LinkedList<T>é uma coleção baseada em ponteiro de nó. No .NET está em implementação duplamente vinculada. Isso significa que os elementos anteriores / próximos têm um link para o elemento atual. E os dados são fragmentados - diferentes objetos de lista podem ser localizados em diferentes locais da RAM. Também haverá mais memória usada para LinkedList<T>que para List<T>ou Array.

  3. List<T>em .Net é a alternativa de Java para ArrayList<T>. Isso significa que este é um wrapper de matriz. Portanto, é alocado na memória como um bloco de dados contíguo. Se o tamanho dos dados alocados exceder 85000 bytes, ele será movido para Heap de Objetos Grandes. Dependendo do tamanho, isso pode levar à fragmentação da pilha (uma forma moderada de vazamento de memória). Mas, ao mesmo tempo, se o tamanho for <85000 bytes - isso fornece uma representação muito compacta e de acesso rápido na memória.

  4. Um único bloco contíguo é preferido para desempenho de acesso aleatório e consumo de memória, mas para coleções que precisam mudar de tamanho regularmente, uma estrutura como uma matriz geralmente precisa ser copiada para um novo local, enquanto uma lista vinculada precisa gerenciar a memória para os recém-inseridos / nós excluídos.

Andrew
fonte
11
Pergunta: Com "dados salvos no array <ou> 85.000 bytes", você quer dizer dados por array / lista ELEMENT, não é? Pode ser entendido que você quer dizer o datasize de toda a matriz ..
Philm
Elementos de matriz localizados sequencialmente na memória. Então, por matriz. Eu sei sobre erro na tabela, mais tarde eu vou corrigi-lo :) (espero ....)
Andrew
Como as listas são lentas nas inserções, se uma lista tem muita recuperação (muitas inserções / exclusões), a memória é ocupada pelo espaço excluído e, nesse caso, isso torna as inserções "re" mais rápidas?
Rob
18

A diferença entre List e LinkedList está na implementação subjacente. Lista é uma coleção baseada em matriz (ArrayList). LinkedList é uma coleção baseada em ponteiro de nó (LinkedListNode). No uso no nível da API, os dois são praticamente os mesmos, pois implementam o mesmo conjunto de interfaces, como ICollection, IEnumerable, etc.

A principal diferença ocorre quando o desempenho é importante. Por exemplo, se você estiver implementando a lista que possui uma operação pesada "INSERT", o LinkedList supera a lista. Como o LinkedList pode fazê-lo em O (1), mas a Lista pode precisar expandir o tamanho da matriz subjacente. Para obter mais informações / detalhes, convém ler a diferença algorítmica entre o LinkedList e as estruturas de dados da matriz. http://en.wikipedia.org/wiki/Linked_list e matriz

Espero que esta ajuda,

user23117
fonte
4
A lista <T> é baseada em matriz (T []), não em ArrayList. Reinsira: o redimensionamento da matriz não é o problema (o algoritmo de duplicação significa que na maioria das vezes não é necessário fazer isso): o problema é que ele deve copiar primeiro todos os dados existentes, o que leva um pouco Tempo.
Marc Gravell
2
@ Marc, o 'algoritmo de duplicação "apenas o torna O (logN), mas ainda é pior que O (1)
Ilya Ryzhenkov 04/10/08
2
O que quero dizer é que não é o redimensionamento que causa a dor - é a felicidade. Então, na pior das hipóteses, se estivermos adicionando o primeiro elemento (zeroth) a cada vez, então a bênção deve mover tudo a cada vez.
Marc Gravell
@IlyaRyzhenkov - você está pensando no caso em que Addestá sempre no final da matriz existente. Listé "bom o suficiente", mesmo que não seja O (1). O sério problema ocorre se você precisar de muitos Adds que não estão no final. Marc está apontando que a necessidade de mover dados existentes toda vez que você insere (não apenas quando o redimensionamento é necessário) é um custo de desempenho mais substancial List.
Home
O problema é que as grandes anotações teóricas não contam toda a história. Na ciência da computação, isso é tudo com que todos se importam, mas há muito mais com o que se preocupar no mundo real.
MattE 08/02/19
11

A principal vantagem das listas vinculadas sobre as matrizes é que os links nos fornecem a capacidade de reorganizar os itens com eficiência. Sedgewick, p. 91

Dr. Alrawi
fonte
11
IMO, esta deve ser a resposta. O LinkedList é usado quando um pedido garantido é importante.
RBaarda
11
@RBaarda: Eu não concordo. Depende do nível que estamos falando. O nível algorítmico é diferente do nível de implementação da máquina. Para consideração de velocidade, você também precisa deste último. Como é apontado, as matrizes são implementadas como "um pedaço" de memória, o que é uma restrição, porque isso pode levar ao redimensionamento e à reorganização da memória, especialmente com matrizes muito grandes. Depois de pensar um pouco, uma estrutura de dados própria e especial, uma lista vinculada de matrizes seria uma idéia para dar um melhor controle sobre a velocidade do preenchimento linear e acessar estruturas de dados muito grandes.
Philm
11
@ Philm - votei no seu comentário, mas gostaria de ressaltar que você está descrevendo um requisito diferente. O que a resposta está dizendo é que a lista vinculada tem vantagem de desempenho para algoritmos que envolvem muita reorganização de itens. Dado isso, interpreto o comentário de RBaarda como referindo-se à necessidade de adicionar / excluir itens, mantendo continuamente uma determinada ordem (critério de classificação). Portanto, não apenas "preenchimento linear". Diante disso, a Lista perde, porque os índices são inúteis (altere toda vez que você adiciona um elemento em qualquer lugar, exceto no final).
Home
4

Uma circunstância comum para usar o LinkedList é assim:

Suponha que você queira remover muitas cadeias de caracteres de uma lista de grandes dimensões, digamos 100.000. As cadeias a serem removidas podem ser consultadas no HashSet dic e acredita-se que a lista de cadeias contenha entre 30.000 a 60.000 dessas cadeias para remover.

Então, qual é o melhor tipo de lista para armazenar as 100.000 cordas? A resposta é LinkedList. Se eles estiverem armazenados em um ArrayList, a iteração e a remoção de Strings correspondentes levarão bilhões de operações, enquanto são necessárias cerca de 100.000 operações usando um iterador e o 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
fonte
6
Você pode simplesmente usar RemoveAllpara remover os itens de um Listsem mover muitos itens ou usar o WhereLINQ para criar uma segunda lista. LinkedListNo entanto, usar um aqui acaba consumindo muito mais memória do que outros tipos de coleções e a perda da localidade da memória significa que será visivelmente mais lento para iterar, tornando-o um pouco pior que a List.
Servy
@ Service, observe que a resposta do @ Tom usa Java. Não tenho certeza se existe um RemoveAllequivalente em Java.
Arturo Torres Sánchez
3
@ ArturoTorresSánchez Bem, a pergunta afirma especificamente que é sobre o .NET, o que torna a resposta muito menos apropriada.
Servy
@ Service, então você deveria ter mencionado isso desde o início.
Arturo Torres Sánchez
Se RemoveAllnão estiver disponível List, você pode fazer um algoritmo de "compactação", que se pareceria com o loop de Tom, mas com dois índices e a necessidade de mover itens para manter um de cada vez na matriz interna da lista. A eficiência é O (n), o mesmo que o algoritmo de Tom LinkedList. Nas duas versões, o tempo para calcular a chave HashSet para as seqüências de caracteres domina. Este não é um bom exemplo de quando usar LinkedList.
Home
2

Quando você precisar de acesso indexado interno, classificação (e após esta pesquisa binária) e o método "ToArray ()", use a Lista.

Michael Damatov
fonte
2

Essencialmente, um List<>no .NET é um invólucro sobre uma matriz . A LinkedList<> é uma lista vinculada . Portanto, a questão se resume a: qual é a diferença entre uma matriz e uma lista vinculada e quando deve ser usada uma matriz em vez de uma lista vinculada. Provavelmente, os dois fatores mais importantes na sua decisão de qual usar se resumiriam a:

  • As listas vinculadas têm um desempenho de inserção / remoção muito melhor, desde que as inserções / remoções não estejam no último elemento da coleção. Isso ocorre porque uma matriz deve mudar todos os elementos restantes que vêm após o ponto de inserção / remoção. No entanto, se a inserção / remoção estiver no final da lista, essa mudança não será necessária (embora a matriz possa precisar ser redimensionada, se sua capacidade for excedida).
  • Matrizes têm recursos de acesso muito melhores. As matrizes podem ser indexadas diretamente (em tempo constante). As listas vinculadas devem ser percorridas (tempo linear).
iliketocode
fonte
1

Isso é adaptado da resposta aceita de Tono Nam , corrigindo algumas medidas erradas.

O teste:

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);
}

E o código:

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();
        }
    }
}

Você pode ver que os resultados estão de acordo com o desempenho teórico que outros documentaram aqui. Muito claro - LinkedList<T>ganha muito tempo em caso de inserções. Não testei a remoção no meio da lista, mas o resultado deve ser o mesmo. Obviamente, existem List<T>outras áreas em que o desempenho é muito melhor, como o acesso aleatório O (1).

nawfal
fonte
0

Use LinkedList<>quando

  1. Você não sabe quantos objetos estão passando pelo portão de inundação. Por exemplo Token Stream,.
  2. Quando você SOMENTE queria excluir \ insert nas extremidades.

Para todo o resto, é melhor usar List<>.

Antony Thomas
fonte
6
Não vejo por que o ponto 2 faz sentido. As listas vinculadas são ótimas quando você faz muitas inserções / exclusões em toda a lista.
Drew Noakes
Devido ao fato de que as LinkedLists não são baseadas em índice, você realmente precisa verificar a lista inteira para inserção ou exclusão que incorra em uma penalidade de O (n). A lista <>, por outro lado, sofre o redimensionamento da matriz, mas ainda assim, o IMO é uma opção melhor quando comparada às LinkedLists.
22812 Antony Thomas
11
Você não precisa procurar na lista por inserções / exclusões se acompanhar os LinkedListNode<T>objetos no seu código. Se você pode fazer isso, é muito melhor do que usar List<T>, especialmente para listas muito longas, onde inserções / remoções são frequentes.
Drew Noakes
Você quer dizer com uma hashtable? Se for esse o caso, essa seria a troca típica de espaço / tempo em que todo programador de computador deve fazer uma escolha com base no domínio do problema :) Mas sim, isso tornaria mais rápido.
Antony Thomas
11
@AntonyThomas - Não, ele quer dizer passando referências a nós em vez de referências a elementos . Se tudo que você tem é um elemento , em seguida, ambos lista e LinkedList tem mau desempenho, porque você tem que procurar. Se você pensa "mas com a Lista, posso apenas passar um índice": isso só é válido quando você nunca insere um novo elemento no meio da Lista. O LinkedList não tem essa limitação, se você mantiver um (e usá- node.Valuelo sempre que quiser o elemento original). Então você reescreve o algoritmo para trabalhar com nós, não com valores brutos.
Home
0

Eu concordo com a maior parte do argumento exposto acima. E também concordo que a Lista parece uma escolha mais óbvia na maioria dos casos.

Mas quero apenas acrescentar que há muitas instâncias em que o LinkedList é uma escolha muito melhor do que List, para uma melhor eficiência.

  1. Suponha que você esteja percorrendo os elementos e deseje executar muitas inserções / exclusões; O LinkedList faz isso em tempo O (n) linear, enquanto List faz isso em tempo O (n ^ 2) quadrático.
  2. Suponha que você queira acessar objetos maiores repetidamente, o LinkedList se torne muito mais útil.
  3. Deque () e fila () são melhor implementadas usando o LinkedList.
  4. Aumentar o tamanho do LinkedList é muito mais fácil e melhor quando você lida com muitos objetos maiores.

Espero que alguém ache esses comentários úteis.

Abhishek
fonte
Observe que este conselho é para .NET, não para Java. Na implementação da lista vinculada de Java, você não tem o conceito de "nó atual", portanto, é necessário percorrer a lista para cada inserção.
Jonathan Allen
Esta resposta está apenas parcialmente correta: 2) se os elementos forem grandes, faça com que o tipo de elemento seja uma classe e não uma estrutura, para que a lista simplesmente contenha uma referência. Então o tamanho do elemento se torna irrelevante. 3) A remoção da fila e da fila pode ser feita com eficiência em uma Lista se você usar a Lista como um "buffer circular", em vez de inserir ou remover na inicialização. Deque de StephenCleary . 4) parcialmente verdadeiro: quando muitos objetos, o pro da LL não precisa de uma memória contígua enorme; desvantagem é a memória extra para ponteiros de nós.
Home
-2

Tantas respostas médias aqui ...

Algumas implementações de lista vinculada usam blocos subjacentes de nós pré-alocados. Se eles não fizerem isso, o tempo constante / tempo linear será menos relevante, pois o desempenho da memória será ruim e o desempenho do cache ainda pior.

Use listas vinculadas quando

1) Você quer segurança da linha. Você pode criar melhores algos seguros para threads. Os custos de bloqueio dominam uma lista de estilos simultâneos.

2) Se você tem uma fila grande como estruturas e deseja remover ou adicionar qualquer lugar, exceto o final, o tempo todo. > 100K listas existem, mas não são tão comuns.

user1496062
fonte
3
Esta pergunta foi sobre duas implementações de C #, não listas vinculadas em geral.
Jonathan Allen
O mesmo em todos os idiomas
user1496062
-2

Fiz uma pergunta semelhante relacionada ao desempenho da coleção LinkedList e descobri que o implemento C # de Steven Cleary do Deque era uma solução. Diferentemente da coleção Fila, o Deque permite ativar / desativar itens na frente e atrás. É semelhante à lista vinculada, mas com desempenho aprimorado.

Adam Cox
fonte
11
Re sua afirmação de que Dequeé "semelhante a lista ligada, mas com melhor desempenho" . Por favor qualificar essa afirmação: Dequeé o desempenho melhor do que LinkedList, para o seu código específico . Seguindo o seu link, vejo que dois dias depois você aprendeu com Ivan Stoev que isso não era uma ineficiência do LinkedList, mas uma ineficiência no seu código. (E mesmo se tivesse sido uma ineficiência de LinkedList, que não justificam uma declaração geral que Deque é mais eficiente, apenas em casos específicos.)
ToolmakerSteve