Quando dovrei usare un Elenco contro un LinkedList


Risposte:


107

modificare

Si prega di leggere i commenti a questa risposta. Le persone affermano che non ho eseguito test adeguati. Sono d'accordo che questa non dovrebbe essere una risposta accettata. Mentre stavo imparando, ho fatto alcuni test e ho avuto voglia di condividerli.

Risposta originale ...

Ho trovato risultati interessanti:

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

Elenco collegato (3,9 secondi)

        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;

Elenco (2,4 secondi)

        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;

Anche se essenzialmente accedi ai dati, è molto più lento !! Dico di non usare mai un Elenco collegato.




Ecco un altro confronto che esegue molti inserimenti (prevediamo di inserire un elemento in mezzo all'elenco)

Elenco collegato (51 secondi)

        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;

Elenco (7,26 secondi)

        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;

Elenco collegato con riferimento alla posizione in cui inserire (0,04 secondi)

        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;

Quindi, solo se hai intenzione di inserire più elementi e hai anche da qualche parte il riferimento di dove intendi inserire l'elemento, usa un elenco collegato. Solo perché devi inserire molti elementi, non è più veloce perché la ricerca della posizione in cui desideri inserirli richiede tempo.


99
C'è un vantaggio in LinkedList over List (questo è .net specifico): poiché l'elenco è supportato da un array interno, viene allocato in un blocco contiguo. Se il blocco allocato supera le dimensioni di 85000 byte, verrà allocato sull'heap di oggetti di grandi dimensioni, una generazione non compattabile. A seconda delle dimensioni, ciò può portare alla frammentazione dell'heap, una lieve forma di perdita di memoria.
JerKimball,

35
Nota che se stai anteponendo molto (come essenzialmente stai facendo nell'ultimo esempio) o eliminando la prima voce, un elenco collegato sarà quasi sempre significativamente più veloce, poiché non c'è alcuna ricerca o spostamento / copia da fare. Una lista richiederebbe di spostare tutto in alto per accogliere il nuovo oggetto, rendendo anticipata un'operazione O (N).
cHao,

6
Perché l'in-loop list.AddLast(a);negli ultimi due esempi di LinkedList? Lo faccio una volta prima del ciclo, come list.AddLast(new Temp(1,1,1,1));nel prossimo ultimo Elenco collegato, ma sembra (per me) come se tu stessi aggiungendo il doppio di oggetti Temp nei loop stessi. (E quando ricontrollo me stesso con un'app di test , abbastanza sicuro, il doppio nella LinkedList.)
ruffin

7
Ho votato in negativo questa risposta. 1) il tuo consiglio generale I say never use a linkedList.è imperfetto come rivela il tuo post successivo. Potresti volerlo modificare. 2) Cosa stai facendo? Istanziazione, aggiunta ed enumerazione in un unico passaggio? Per lo più, l'istanza e l'enumerazione non sono ciò di cui i ppl sono preoccupati, quelli sono passi di una volta. In particolare, il tempismo degli inserti e delle aggiunte darebbe un'idea migliore. 3) Soprattutto, stai aggiungendo più del necessario a un elenco di link. Questo è un confronto sbagliato. Diffonde un'idea sbagliata sull'elenco collegato.
nawfal,

47
Ci dispiace, ma questa risposta è davvero pessima. Per favore NON ascoltare questa risposta. Motivo in breve: è completamente errato pensare che le implementazioni di elenchi supportate da array siano abbastanza stupide da ridimensionare l'array ad ogni inserimento. Gli elenchi collegati sono naturalmente più lenti degli elenchi supportati da array durante l'attraversamento e durante l'inserimento a entrambe le estremità, poiché solo loro hanno bisogno di creare nuovi oggetti, mentre gli elenchi supportati da array utilizzano un buffer (in entrambe le direzioni, ovviamente). I benchmark (eseguiti male) indicano esattamente questo. La risposta non riesce a controllare completamente i casi in cui sono preferibili elenchi collegati!
mafu,

277

Nella maggior parte dei casi, List<T>è più utile. LinkedList<T>avrà un costo inferiore quando si aggiungono / rimuovono gli elementi in mezzo alla lista, mentre List<T>possono solo aggiungere / rimuovere a buon mercato alla fine della lista.

LinkedList<T>è più efficace solo se si accede a dati sequenziali (in avanti o all'indietro) - l'accesso casuale è relativamente costoso poiché deve camminare ogni volta sulla catena (quindi perché non ha un indicizzatore). Tuttavia, poiché a List<T>è essenzialmente solo un array (con un wrapper), l'accesso casuale va bene.

List<T>offre anche un sacco di metodi di supporto - Find, ToArray, ecc; tuttavia, questi sono disponibili anche LinkedList<T>con .NET 3.5 / C # 3.0 tramite metodi di estensione, quindi è un fattore meno importante.


4
Un vantaggio di List <> rispetto a LinkedList <> di cui non avevo mai pensato riguarda il modo in cui i microprocessori implementano la memorizzazione nella cache della memoria. Anche se non lo capisco del tutto, lo scrittore di questo articolo del blog parla molto di "località di riferimento", il che rende attraversare un array molto più velocemente rispetto a un elenco collegato, almeno se l'elenco collegato è diventato un po 'frammentato nella memoria . kjellkod.wordpress.com/2012/02/25/…
RenniePet

L'elenco @RenniePet è implementato con un array dinamico e gli array sono blocchi contigui di memoria.
Casey,

2
Poiché List è un array dinamico, è per questo che a volte è bene specificare la capacità di un List nel costruttore se lo si conosce in anticipo.
Cardin Lee JH,

È possibile che l'implementazione C # di tutto, array, List <T> e LinkedList <T> sia in qualche modo non ottimale per un caso molto importante: è necessario un elenco molto grande, append (AddLast) e traversal sequenziale (in una direzione) sono totalmente bene: non voglio ridimensionare l'array per ottenere blocchi continui (è garantito per ogni array, anche array da 20 GB?), e non conosco in anticipo la dimensione, ma posso indovinare in anticipo una dimensione del blocco, ad esempio 100 MB da prenotare ogni volta in anticipo. Questa sarebbe una buona implementazione. Oppure array / List è simile a quello e ho perso un punto?
Philm

1
@Philm è il tipo di scenario in cui scrivi il tuo spessore sulla strategia di blocco scelta; List<T>e T[]fallirà per essere troppo grosso (tutta una lastra), LinkedList<T>gemerà per essere troppo granulare (lastra per elemento).
Marc Gravell

212

Pensare a un elenco collegato come un elenco può essere un po 'fuorviante. È più come una catena. In effetti, in .NET, LinkedList<T>non implementa nemmenoIList<T> . Non esiste un vero concetto di indice in un elenco collegato, anche se può sembrare che esista. Certamente nessuno dei metodi forniti nella classe accetta gli indici.

Le liste collegate possono essere singolarmente collegate o doppiamente collegate. Questo si riferisce al fatto che ogni elemento nella catena abbia un collegamento solo al successivo (singolarmente collegato) o ad entrambi gli elementi precedenti / successivi (doppiamente collegati). LinkedList<T>è doppiamente collegato.

Internamente, List<T>è supportato da un array. Ciò fornisce una rappresentazione molto compatta in memoria. Al contrario, LinkedList<T>comporta memoria aggiuntiva per memorizzare i collegamenti bidirezionali tra elementi successivi. Quindi l'impronta di memoria di a LinkedList<T>sarà generalmente più grande di for List<T>(con l'avvertenza che List<T>può avere elementi di array interni inutilizzati per migliorare le prestazioni durante le operazioni di aggiunta).

Hanno anche diverse caratteristiche prestazionali:

Aggiungere

  • LinkedList<T>.AddLast(item) tempo costante
  • List<T>.Add(item) tempo costante ammortizzato, caso peggiore lineare

anteporre

  • LinkedList<T>.AddFirst(item) tempo costante
  • List<T>.Insert(0, item) tempo lineare

Inserimento

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

Rimozione

  • LinkedList<T>.Remove(item) tempo lineare
  • LinkedList<T>.Remove(node) tempo costante
  • List<T>.Remove(item) tempo lineare
  • List<T>.RemoveAt(index) tempo lineare

Contare

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

contiene

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

Chiaro

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

Come puoi vedere, sono per lo più equivalenti. In pratica, l'API diLinkedList<T> è più ingombrante da usare e i dettagli delle sue esigenze interne si riversano nel tuo codice.

Tuttavia, se è necessario eseguire molti inserimenti / rimozioni all'interno di un elenco, offre un tempo costante. List<T>offre un tempo lineare, poiché gli elementi aggiuntivi nell'elenco devono essere rimescolati dopo l'inserimento / la rimozione.


2
Il conteggio della lista collegata è costante? Ho pensato che sarebbe stato lineare?
Iain Ballard,

10
@Iain, il conteggio viene memorizzato nella cache in entrambe le classi di elenco.
Drew Noakes,

3
Hai scritto che "Elenco <T> .Aggiungi (elemento) tempo logaritmico", tuttavia è in effetti "Costante" se la capacità dell'elenco può memorizzare il nuovo elemento e "Lineare" se l'elenco non ha spazio sufficiente e nuovo essere riallocato.
aStranger

@aStraniere, ovviamente hai ragione. Non sono sicuro di quello che stavo pensando in precedenza - forse che il tempo normale ammortizzato del caso è logaritmico, cosa che non lo è. In effetti il ​​tempo ammortizzato è costante. Non sono entrato nel caso migliore / peggiore delle operazioni, con l'obiettivo di un semplice confronto. Penso che l'operazione di aggiunta sia abbastanza significativa da fornire questo dettaglio. Modifica la risposta. Grazie.
Drew Noakes,

1
@Philm, dovresti eventualmente iniziare una nuova domanda e non dire come utilizzerai questa struttura di dati una volta creata, ma se stai parlando di un milione di righe potresti apprezzare una sorta di ibrido (elenco collegato di blocchi di array o simili) per ridurre la frammentazione dell'heap, ridurre il sovraccarico di memoria ed evitare un unico enorme oggetto sul LOH.
Drew Noakes,

118

Gli elenchi collegati consentono di inserire o eliminare molto rapidamente un membro dell'elenco. Ogni membro in un elenco collegato contiene un puntatore al membro successivo nell'elenco in modo da inserire un membro nella posizione i:

  • aggiorna il puntatore nel membro i-1 per puntare al nuovo membro
  • imposta il puntatore nel nuovo membro in modo che punti al membro i

Lo svantaggio di un elenco collegato è che l'accesso casuale non è possibile. Per accedere a un membro è necessario attraversare l'elenco fino a quando non viene trovato il membro desiderato.


6
Vorrei aggiungere che gli elenchi collegati hanno un overhead per articolo archiviato implicitamente sopra tramite LinkedListNode che fa riferimento al nodo precedente e successivo. Il payoff di questo è un blocco contiguo di memoria non è necessario per memorizzare l'elenco, a differenza di un elenco basato su array.
paulecoyote,

3
Un blocco contiguo di memoria non è di solito attribuito?
Jonathan Allen,

7
Sì, un blocco contiguo è preferito per le prestazioni di accesso casuale e il consumo di memoria, ma per le raccolte che devono cambiare regolarmente dimensione, una struttura come un array generalmente deve essere copiata in una nuova posizione mentre un elenco collegato deve solo gestire la memoria per il nodi appena inseriti / eliminati.
jpierson,

6
Se hai mai dovuto lavorare con array o elenchi molto grandi (un elenco avvolge solo un array) inizierai a incorrere in problemi di memoria anche se sembra che ci sia molta memoria disponibile sul tuo computer. L'elenco utilizza una strategia di raddoppio quando alloca nuovo spazio nel suo array sottostante. Quindi un array elemnt 1000000 pieno verrà copiato in un nuovo array con 2000000 elementi. Questo nuovo array deve essere creato in uno spazio di memoria contiguo che è abbastanza grande da mantenerlo.
Andrew,

1
Ho avuto un caso specifico in cui tutto quello che ho fatto è stato l'aggiunta e la rimozione e il looping uno per uno ... qui l'elenco collegato era di gran lunga superiore all'elenco normale ..
Peter,

26

La mia risposta precedente non era abbastanza precisa. Come veramente è stato orribile: D Ma ora posso pubblicare una risposta molto più utile e corretta.


Ho fatto alcuni test aggiuntivi. Puoi trovarne l'origine tramite il seguente link e ricontrollarlo sul tuo ambiente da solo: https://github.com/ukushu/DataStructuresTestsAndOther.git

Brevi risultati:

  • La matrice deve usare:

    • Il più spesso possibile. È veloce e richiede il più piccolo intervallo di RAM per la stessa quantità di informazioni.
    • Se conosci il conteggio esatto delle celle necessarie
    • Se i dati sono stati salvati nell'array <85000 b (85000/32 = 2656 elementi per dati interi)
    • Se necessario alta velocità di accesso casuale
  • L'elenco deve usare:

    • Se necessario per aggiungere celle alla fine dell'elenco (spesso)
    • Se necessario per aggiungere celle all'inizio / al centro dell'elenco (NON SPESSO)
    • Se i dati sono stati salvati nell'array <85000 b (85000/32 = 2656 elementi per dati interi)
    • Se necessario alta velocità di accesso casuale
  • LinkedList deve usare:

    • Se necessario per aggiungere celle all'inizio / al centro / alla fine dell'elenco (spesso)
    • Se necessario solo accesso sequenziale (avanti / indietro)
    • Se devi salvare oggetti GRANDI, ma il numero di oggetti è basso.
    • Meglio non usare per una grande quantità di elementi, in quanto utilizza memoria aggiuntiva per i collegamenti.

Più dettagli:

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

  1. LinkedList<T>internamente non è un elenco in .NET. Non è nemmeno implementare IList<T>. Ed è per questo che mancano indici e metodi relativi agli indici.

  2. LinkedList<T>è una raccolta basata su puntatore nodo. In .NET è in implementazione doppiamente collegata. Ciò significa che gli elementi precedenti / successivi hanno un collegamento all'elemento corrente. E i dati sono frammentati: diversi oggetti della lista possono trovarsi in diversi punti della RAM. Inoltre ci sarà più memoria utilizzata per LinkedList<T>che per List<T>o Array.

  3. List<T>in .Net è l'alternativa di Java di ArrayList<T>. Ciò significa che si tratta di wrapper di array. Quindi è allocato in memoria come un blocco contiguo di dati. Se la dimensione dei dati allocati supera 85000 byte, verrà spostata nell'heap di oggetti di grandi dimensioni. A seconda delle dimensioni, ciò può portare alla frammentazione dell'heap (una forma lieve di perdita di memoria). Ma allo stesso tempo se la dimensione <85000 byte - questo fornisce una rappresentazione molto compatta e ad accesso rapido in memoria.

  4. Il blocco singolo contiguo è preferito per le prestazioni di accesso casuale e il consumo di memoria, ma per le raccolte che devono cambiare regolarmente dimensione una struttura come una matrice deve generalmente essere copiata in una nuova posizione mentre un elenco collegato deve solo gestire la memoria per il nuovo inserito / nodi eliminati.


1
Domanda: Con "dati salvati nell'array <o> 85.000 byte" intendi i dati per array / elenco ELEMENT? Si potrebbe capire che si intende la dimensione dei dati dell'intero array ..
Philm

Elementi di matrice situati in sequenza nella memoria. Quindi per array. So di un errore nella tabella, in seguito lo risolverò :) (Spero ....)
Andrew,

Con gli elenchi lenti negli inserimenti, se un elenco presenta molti cambiamenti (molti inserimenti / eliminazioni) la memoria viene occupata dallo spazio eliminato e, in tal caso, ciò rende gli "re" inserimenti più veloci?
Rob

18

La differenza tra List e LinkedList risiede nella loro implementazione sottostante. L'elenco è una raccolta basata su array (ArrayList). LinkedList è una raccolta basata su puntatore nodo (LinkedListNode). A livello di API, entrambi sono praticamente uguali poiché entrambi implementano lo stesso set di interfacce come ICollection, IEnumerable, ecc.

La differenza fondamentale viene quando le prestazioni contano. Ad esempio, se si sta implementando l'elenco con un'operazione "INSERT" pesante, LinkedList supera l'elenco. Dato che LinkedList può farlo in O (1), potrebbe essere necessario che List aumenti le dimensioni dell'array sottostante. Per ulteriori informazioni / dettagli, è possibile leggere la differenza algoritmica tra LinkedList e le strutture di dati dell'array. http://it.wikipedia.org/wiki/Linked_list e Array

Spero che questo aiuto,


4
Elenco <T> è basato su array (T []), non su ArrayList. Reinserisci: il ridimensionamento dell'array non è il problema (l'algoritmo di raddoppio significa che il più delle volte non è necessario): il problema è che deve prima copiare a blocchi tutti i dati esistenti, il che richiede un po ' tempo.
Marc Gravell

2
@Marc, l '"algoritmo di raddoppio" lo rende solo O (logN), ma è ancora peggio di O (1)
Ilya Ryzhenkov,

2
Il mio punto era che non è il ridimensionamento a causare il dolore, ma la colpa. Nel peggiore dei casi, se stiamo aggiungendo il primo elemento (zeroth) ogni volta, allora il matto deve spostare tutto ogni volta.
Marc Gravell

@IlyaRyzhenkov - stai pensando al caso in cui Addè sempre alla fine dell'array esistente. Listè "abbastanza buono", anche se non O (1). Il grave problema si verifica se hai bisogno di molti messaggi Addche non sono alla fine. Marc sta sottolineando che la necessità di spostare i dati esistenti ogni volta che si inserisce (non solo quando è necessario il ridimensionamento) è un costo delle prestazioni più sostanziale di List.
ToolmakerSteve

Il problema è che le notazioni teoriche Big O non raccontano l'intera storia. Nell'informatica tutto ciò di cui tutti si preoccupano, ma nel mondo reale c'è molto di più di cui preoccuparsi.
MattE

11

Il vantaggio principale degli elenchi collegati rispetto agli array è che i collegamenti ci offrono la possibilità di riorganizzare gli elementi in modo efficiente. Sedgewick, p. 91


1
IMO questa dovrebbe essere la risposta. LinkedList viene utilizzato quando un ordine garantito è importante.
RBaarda,

1
@Raaarda: non sono d'accordo. Dipende dal livello di cui stiamo parlando. Il livello algoritmico è diverso dal livello di implementazione della macchina. Per una rapida considerazione è necessario anche quest'ultimo. Come è stato sottolineato, le matrici sono implementate come "un pezzo" di memoria che è una restrizione, perché ciò può portare al ridimensionamento e alla riorganizzazione della memoria, specialmente con matrici molto grandi. Dopo aver pensato per un po ', una speciale struttura di dati propria, un elenco collegato di array sarebbe un'idea per dare un migliore controllo sulla velocità di riempimento lineare e l'accesso a strutture di dati molto grandi.
Phil

1
@Philm - Ho votato a favore del tuo commento, ma vorrei sottolineare che stai descrivendo un requisito diverso. Ciò che la risposta sta dicendo è che l'elenco collegato presenta un vantaggio in termini di prestazioni per gli algoritmi che comportano un sacco di riordino degli articoli. Detto questo, interpreto il commento di RBaarda come riferito alla necessità di aggiungere / eliminare articoli mantenendo continuamente un determinato ordine (criteri di ordinamento). Quindi non solo "riempimento lineare". Detto questo, List perde, perché gli indici sono inutili (cambiano ogni volta che aggiungi un elemento ovunque ma alla fine della coda).
ToolmakerSteve

4

Una circostanza comune per usare LinkedList è la seguente:

Supponiamo di voler rimuovere molte determinate stringhe da un elenco di stringhe di grandi dimensioni, ad esempio 100.000. Le stringhe da rimuovere possono essere cercate in HashSet dic e si ritiene che l'elenco delle stringhe contenga tra le 30.000 e le 60.000 stringhe da rimuovere.

Allora qual è il miglior tipo di elenco per memorizzare le 100.000 stringhe? La risposta è LinkedList. Se vengono archiviati in un ArrayList, l'iterazione su di esso e la rimozione delle stringhe corrispondenti richiederebbe miliardi di operazioni, mentre impiega circa 100.000 operazioni utilizzando un iteratore e il metodo 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();
}

6
Puoi semplicemente usare RemoveAllper rimuovere gli elementi da a Listsenza spostare molti oggetti in giro, oppure usare Whereda LINQ per creare un secondo elenco. L'uso di un LinkedListqui finisce tuttavia per consumare una quantità di memoria notevolmente maggiore rispetto ad altri tipi di raccolte e la perdita della localizzazione della memoria significa che sarà notevolmente più lenta da iterare, rendendolo un po 'peggio di un List.
Servito il

@Servy, nota che la risposta di @ Tom usa Java. Non sono sicuro che ci sia un RemoveAllequivalente in Java.
Arturo Torres Sánchez,

3
@ ArturoTorresSánchez Beh, la domanda afferma specificamente che si tratta di .NET, quindi la risposta è molto meno appropriata.
Servito il

@Servy, allora avresti dovuto menzionarlo dall'inizio.
Arturo Torres Sánchez,

Se RemoveAllnon è disponibile per List, potresti fare un algoritmo di "compattazione", che assomiglierebbe al loop di Tom, ma con due indici e la necessità di spostare gli elementi da tenere uno alla volta nell'array interno dell'elenco. L'efficienza è O (n), uguale all'algoritmo di Tom per LinkedList. In entrambe le versioni, domina il tempo per calcolare la chiave HashSet per le stringhe. Questo non è un buon esempio di quando usare LinkedList.
ToolmakerSteve

2

Quando hai bisogno di accesso indicizzato, ordinamento (e dopo questa ricerca binaria) incorporato e metodo "ToArray ()", dovresti usare Elenco.


2

Essenzialmente, a List<>in .NET è un wrapper su un array . A LinkedList<> è un elenco collegato . Quindi la domanda si riduce a: qual è la differenza tra un array e un elenco collegato e quando dovrebbe essere usato un array invece di un elenco collegato. Probabilmente i due fattori più importanti nella tua decisione di quale utilizzare sarebbero dovuti a:

  • Gli elenchi collegati hanno prestazioni di inserzione / rimozione molto migliori, a condizione che gli inserimenti / le rimozioni non si trovino sull'ultimo elemento della raccolta. Questo perché un array deve spostare tutti gli elementi rimanenti che seguono il punto di inserimento / rimozione. Se l'inserimento / la rimozione si trova alla fine dell'elenco, tuttavia, questo spostamento non è necessario (anche se l'array potrebbe dover essere ridimensionato, se la sua capacità viene superata).
  • Gli array hanno capacità di accesso molto migliori. Le matrici possono essere indicizzate direttamente (a tempo costante). Le liste collegate devono essere attraversate (tempo lineare).

1

Questo è adattato da Tono Nam risposta accettata di correggendo alcune misurazioni errate al suo interno.

Il test:

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 il codice:

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

Puoi vedere che i risultati sono conformi alle prestazioni teoriche che altri hanno documentato qui. Abbastanza chiaro: LinkedList<T>guadagna molto tempo in caso di inserzioni. Non ho testato la rimozione dalla metà dell'elenco, ma il risultato dovrebbe essere lo stesso. Ovviamente List<T>ha altre aree in cui si comporta in modo migliore come O (1) accesso casuale.


0

Utilizzare LinkedList<>quando

  1. Non sai quanti oggetti stanno attraversando la porta dell'inondazione. Ad esempio Token Stream,.
  2. Quando volevi SOLO eliminare \ insert alle estremità.

Per tutto il resto, è meglio usare List<>.


6
Non vedo perché il punto 2 abbia senso. Gli elenchi collegati sono eccezionali quando si eseguono molti inserimenti / eliminazioni nell'intero elenco.
Drew Noakes,

A causa del fatto che gli elenchi collegati non sono basati sull'indice, è necessario sottoporre a scansione l'intero elenco per l'inserimento o l'eliminazione che comporta una penalità O (n). L'elenco <> d'altra parte soffre di ridimensionamento dell'array, ma IMO è ancora un'opzione migliore rispetto a LinkedLists.
Antony Thomas,

1
Non è necessario eseguire la scansione dell'elenco per inserimenti / eliminazioni se si tiene traccia degli LinkedListNode<T>oggetti nel codice. Se riesci a farlo, allora è molto meglio dell'uso List<T>, specialmente per elenchi molto lunghi in cui sono frequenti inserimenti / rimozioni.
Drew Noakes,

Intendi attraverso un hashtable? In tal caso, sarebbe il tipico compromesso spazio-tempo che ogni programmatore di computer dovrebbe fare una scelta in base al dominio problematico :) Ma sì, lo renderebbe più veloce.
Antony Thomas,

1
@AntonyThomas - No, intende passare attorno ai riferimenti ai nodi invece di passare ai riferimenti agli elementi . Se tutto ciò che hai è un elemento , sia List che LinkedList hanno prestazioni scadenti, perché devi cercare. Se pensi "ma con Elenco posso solo passare un indice": questo è valido solo quando non inserisci mai un nuovo elemento nel mezzo dell'elenco. LinkedList non ha questa limitazione, se tieni un nodo (e lo usi node.Valuequando vuoi l'elemento originale). Quindi riscrivi l'algoritmo per lavorare con i nodi, non con i valori non elaborati.
ToolmakerSteve

0

Sono d'accordo con la maggior parte del punto sopra esposto. E sono anche d'accordo sul fatto che Elenco sembri una scelta più ovvia nella maggior parte dei casi.

Ma voglio solo aggiungere che ci sono molti casi in cui LinkedList è una scelta di gran lunga migliore rispetto a List per una migliore efficienza.

  1. Supponiamo che tu stia attraversando gli elementi e desideri eseguire molti inserimenti / eliminazioni; LinkedList lo fa in tempo O (n) lineare, mentre List lo fa in tempo O quadratico (n ^ 2).
  2. Supponiamo che tu voglia accedere più e più volte a oggetti più grandi, LinkedList diventerà molto più utile.
  3. Deque () e queue () sono implementati meglio usando LinkedList.
  4. Aumentare le dimensioni di LinkedList è molto più facile e migliore una volta che hai a che fare con oggetti più grandi.

Spero che qualcuno possa trovare utili questi commenti.


Nota che questo consiglio è per .NET, non per Java. Nell'implementazione dell'elenco collegato di Java non hai il concetto di "nodo corrente", quindi devi attraversare l'elenco per ogni singolo inserimento.
Jonathan Allen,

Questa risposta è solo parzialmente corretta: 2) se gli elementi sono di grandi dimensioni, quindi rendere l'elemento di tipo una Classe non uno Struct, in modo che List contenga semplicemente un riferimento. Quindi la dimensione dell'elemento diventa irrilevante. 3) Deque e la coda possono essere eseguite in modo efficiente in un Elenco se si utilizza Elenco come "buffer circolare", anziché inserire o rimuovere all'avvio. StephenCleary Deque . 4) parzialmente vero: quando molti oggetti, pro di LL non hanno bisogno di un'enorme memoria contigua; il rovescio della medaglia è la memoria aggiuntiva per i puntatori del nodo.
ToolmakerSteve

-2

Così tante risposte medie qui ...

Alcune implementazioni di elenchi collegati utilizzano blocchi sottostanti di nodi pre allocati. In caso contrario, il tempo costante / tempo lineare è meno rilevante in quanto le prestazioni della memoria saranno scarse e le prestazioni della cache peggioreranno ulteriormente.

Utilizzare gli elenchi collegati quando

1) Volete la sicurezza del thread. Puoi creare algos thread thread migliori. I costi di blocco domineranno un elenco di stili simultanei.

2) Se si dispone di una grande coda come le strutture e si desidera rimuovere o aggiungere ovunque ma alla fine continuamente. > Esistono 100.000 elenchi ma non sono così comuni.


3
Questa domanda riguardava due implementazioni C #, non elenchi collegati in generale.
Jonathan Allen,

Lo stesso in ogni lingua
user1496062

-2

Ho posto una domanda simile relativa alle prestazioni della collezione LinkedList e ho scoperto che l'attrezzo C # di Deque di Steven Cleary era una soluzione. A differenza della collezione Queue, Deque consente di spostare gli oggetti avanti / indietro davanti e dietro. È simile all'elenco collegato, ma con prestazioni migliorate.


1
Re la sua dichiarazione che Dequeè "simile a lista collegata, ma con prestazioni migliorate" . Si prega di qualificare questa affermazione: Dequeè la prestazione migliore rispetto LinkedList, per il vostro codice specifico . Seguendo il tuo link, vedo che due giorni dopo hai appreso da Ivan Stoev che questa non era un'inefficienza di LinkedList, ma un'inefficienza nel tuo codice. (E anche se fosse stata un'inefficienza di LinkedList, ciò non giustificherebbe un'affermazione generale secondo cui Deque è più efficiente; solo in casi specifici.)
ToolmakerSteve
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.