Kiedy lepiej jest użyć Lista vs w LinkedList ?
c#
.net
vb.net
data-structures
linked-list
Jonathan Allen
źródło
źródło
Odpowiedzi:
Edytować
Oryginalna odpowiedź ...
Znalazłem ciekawe wyniki:
Powiązana lista (3,9 sekundy)
Lista (2,4 sekundy)
Nawet jeśli uzyskujesz dostęp do danych w zasadzie, jest to znacznie wolniejsze !! Mówię, że nigdy nie używaj LinkedList.
Oto kolejne porównanie wykonujące wiele wstawek (planujemy wstawić element na środku listy)
Lista połączona (51 sekund)
Lista (7,26 sekund)
Lista połączona z odniesieniem do lokalizacji, w której należy wstawić (.04 sekundy)
Więc tylko jeśli planujesz wstawić kilka elementów, a także gdzieś masz odniesienie do miejsca, w którym planujesz wstawić element, użyj listy połączonej. Tylko dlatego, że musisz wstawić wiele elementów, nie przyspiesza to, ponieważ wyszukiwanie miejsca, w którym chcesz je wstawić, zajmuje dużo czasu.
źródło
list.AddLast(a);
w dwóch ostatnich przykładach LinkedList? Robię to raz przed pętlą, jaklist.AddLast(new Temp(1,1,1,1));
w przypadku następnej do ostatniej LinkedList, ale wygląda (dla mnie), jakbyś dodawał dwa razy więcej obiektów Temp w samych pętlach. (A kiedy sprawdzę się dwukrotnie za pomocą aplikacji testowej , to na pewno dwa razy więcej na LinkedList.)I say never use a linkedList.
jest wadliwa, jak ujawnia twój późniejszy post. Możesz go edytować. 2) Jaki masz czas? Tworzenie instancji, dodawanie i wyliczanie łącznie w jednym kroku? Przeważnie tworzenie instancji i wyliczanie nie są tym, czym martwi się ppl, są to jednorazowe kroki. Dokładniej mówiąc, wstawianie i dodawanie wstawek w czasie dałoby lepszy pomysł. 3) Co najważniejsze, dodajesz więcej niż to wymagane do listy połączonej. To jest złe porównanie. Rozpowszechnia błędne wyobrażenie o linkowanej liście.W większości przypadków
List<T>
jest bardziej przydatny.LinkedList<T>
będzie miał mniejszy koszt podczas dodawania / usuwania elementów na środku listy, podczas gdyList<T>
może tylko tanio dodawać / usuwać na końcu listy.LinkedList<T>
jest najskuteczniejszy tylko wtedy, gdy uzyskujesz dostęp do danych sekwencyjnych (do przodu lub do tyłu) - dostęp losowy jest stosunkowo drogi, ponieważ za każdym razem musi przechodzić przez łańcuch (dlatego nie ma indeksatora). Ponieważ jednakList<T>
jest w zasadzie tylko tablicą (z opakowaniem), dostęp losowy jest w porządku.List<T>
również oferuje wiele sposobów wsparcia -Find
,ToArray
itp; są one jednak dostępne również wLinkedList<T>
przypadku .NET 3.5 / C # 3.0 za pomocą metod rozszerzenia - więc jest to mniej istotny czynnik.źródło
List<T>
iT[]
nie powiedzie się, że jest zbyt gruby (wszystkie płyty),LinkedList<T>
będzie lamentować, że jest zbyt ziarnisty (płyta na element).Myślenie o połączonej liście jako liście może być nieco mylące. To bardziej jak łańcuch. W rzeczywistości .NET
LinkedList<T>
nawet nie implementujeIList<T>
. Na połączonej liście nie ma prawdziwej koncepcji indeksu, chociaż może się wydawać, że tak jest. Z pewnością żadna z metod podanych w klasie nie akceptuje indeksów.Listy połączone mogą być połączone pojedynczo lub podwójnie. Odnosi się to do tego, czy każdy element w łańcuchu ma link tylko do następnego (pojedynczo połączony) czy do obu poprzednich / następnych elementów (podwójnie połączony).
LinkedList<T>
jest podwójnie powiązany.Wewnętrznie
List<T>
jest wspierany przez tablicę. Zapewnia to bardzo zwartą reprezentację w pamięci. I odwrotnie,LinkedList<T>
wymaga dodatkowej pamięci do przechowywania dwukierunkowych połączeń między kolejnymi elementami. Tak więc ślad pamięci aLinkedList<T>
będzie na ogół większy niż dlaList<T>
(z zastrzeżeniem, żeList<T>
może mieć nieużywane elementy wewnętrznej tablicy w celu poprawy wydajności podczas operacji dołączania).Mają też różne parametry wydajnościowe:
Dodać
LinkedList<T>.AddLast(item)
stały czasList<T>.Add(item)
zamortyzowany stały czas, najgorszy przypadek liniowyPrzygotuj
LinkedList<T>.AddFirst(item)
stały czasList<T>.Insert(0, item)
czas liniowyWprowadzenie
LinkedList<T>.AddBefore(node, item)
stały czasLinkedList<T>.AddAfter(node, item)
stały czasList<T>.Insert(index, item)
czas liniowyUsuwanie
LinkedList<T>.Remove(item)
czas liniowyLinkedList<T>.Remove(node)
stały czasList<T>.Remove(item)
czas liniowyList<T>.RemoveAt(index)
czas liniowyLiczyć
LinkedList<T>.Count
stały czasList<T>.Count
stały czasZawiera
LinkedList<T>.Contains(item)
czas liniowyList<T>.Contains(item)
czas liniowyJasny
LinkedList<T>.Clear()
czas liniowyList<T>.Clear()
czas liniowyJak widać, są one w większości równoważne. W praktyce interfejs API
LinkedList<T>
jest trudniejszy w użyciu, a szczegóły jego wewnętrznych potrzeb rozlewają się do twojego kodu.Jeśli jednak musisz wykonać wiele operacji wstawiania / usuwania z listy, oferuje ona stały czas.
List<T>
oferuje czas liniowy, ponieważ dodatkowe elementy na liście muszą zostać przetasowane po wstawieniu / usunięciu.źródło
Listy połączone zapewniają bardzo szybkie wstawianie lub usuwanie członka listy. Każdy członek na połączonej liście zawiera wskaźnik do następnego członka na liście, aby wstawić członka w pozycji i:
Wadą połączonej listy jest to, że losowy dostęp nie jest możliwy. Dostęp do członka wymaga przemierzania listy aż do znalezienia żądanego członka.
źródło
Moja poprzednia odpowiedź nie była wystarczająco dokładna. Naprawdę było to okropne: D Ale teraz mogę opublikować o wiele bardziej przydatną i poprawną odpowiedź.
Zrobiłem kilka dodatkowych testów. Możesz znaleźć jego źródło, klikając poniższy link i ponownie sprawdź go w swoim środowisku: https://github.com/ukushu/DataStructuresTestsAndOther.git
Krótkie wyniki:
Tablica musi używać:
Lista musi użyć:
LinkedList musi użyć:
Więcej szczegółów:
Ciekawe wiedzieć:
LinkedList<T>
wewnętrznie nie jest listą w .NET. To nawet nie implementujeIList<T>
. I dlatego nie ma indeksów i metod związanych z indeksami.LinkedList<T>
to kolekcja oparta na wskaźnikach węzłów. W .NET jest w podwójnie powiązanej implementacji. Oznacza to, że poprzednie / następne elementy mają link do bieżącego elementu. Dane są pofragmentowane - różne obiekty listy mogą znajdować się w różnych miejscach pamięci RAM. Będzie także wykorzystywana większa pamięćLinkedList<T>
niż dlaList<T>
lub dla macierzy.List<T>
w .Net jest alternatywą Java dlaArrayList<T>
. Oznacza to, że jest to opakowanie tablicy. Jest więc przydzielany w pamięci jako jeden ciągły blok danych. Jeśli przydzielony rozmiar danych przekracza 85000 bajtów, zostanie przeniesiony do sterty dużych obiektów. W zależności od wielkości może to prowadzić do fragmentacji sterty (łagodna forma wycieku pamięci). Ale w tym samym czasie, jeśli rozmiar <85000 bajtów - zapewnia bardzo kompaktową i szybką reprezentację w pamięci.Pojedynczy ciągły blok jest preferowany dla wydajności dostępu losowego i zużycia pamięci, ale w kolekcjach, które muszą regularnie zmieniać rozmiar, struktura taka jak tablica zazwyczaj musi zostać skopiowana do nowej lokalizacji, podczas gdy połączona lista musi zarządzać pamięcią tylko dla nowo wstawionego / usunięte węzły.
źródło
Różnica między List a LinkedList polega na ich podstawowej implementacji. Lista jest kolekcją opartą na tablicy (ArrayList). LinkedList to kolekcja oparta na wskaźnikach węzłów (LinkedListNode). Na poziomie interfejsu API oba są prawie takie same, ponieważ oba implementują ten sam zestaw interfejsów, takich jak ICollection, IEnumerable itp.
Kluczowa różnica pojawia się, gdy liczy się wydajność. Na przykład, jeśli implementujesz listę, która ma ciężką operację „WSTAW”, LinkedList przewyższa Listę. Ponieważ LinkedList może to zrobić w czasie O (1), jednak List może wymagać rozszerzenia rozmiaru podstawowej tablicy. Aby uzyskać więcej informacji / szczegółów, możesz przeczytać o różnicy algorytmicznej między LinkedList a strukturami tablicowymi. http://en.wikipedia.org/wiki/Linked_list and Array
Mam nadzieję, że to pomoże,
źródło
Add
zawsze znajduje się na końcu istniejącej tablicy.List
jest w tym „wystarczająco dobry”, nawet jeśli nie O (1). Poważny problem występuje, jeśli potrzebujesz wieluAdd
, które nie są na końcu. Marc podkreśla, że potrzeba przenoszenia istniejących danych za każdym razem, gdy wstawiasz (nie tylko wtedy, gdy konieczna jest zmiana rozmiaru), jest bardziej znaczącym kosztem wydajnościList
.Podstawową zaletą list połączonych w porównaniu z tablicami jest to, że linki zapewniają nam możliwość efektywnego rozmieszczania elementów. Sedgewick, p. 91
źródło
Typowa okoliczność korzystania z LinkedList wygląda następująco:
Załóżmy, że chcesz usunąć wiele określonych ciągów z listy ciągów o dużym rozmiarze, powiedzmy 100 000. Ciągi do usunięcia można sprawdzić w HashSet dic, a lista ciągów zawiera od 30 000 do 60 000 takich ciągów do usunięcia.
Jaki jest zatem najlepszy rodzaj Listy do przechowywania 100 000 Ciągów? Odpowiedź to LinkedList. Jeśli są one przechowywane w ArrayList, iteracja nad nim i usuwanie dopasowanych ciągów może zająć miliardy operacji, podczas gdy zajmie to około 100 000 operacji za pomocą iteratora i metody remove ().
źródło
RemoveAll
aby usunąć elementyList
bez przenoszenia wielu elementów lub użyćWhere
LINQ, aby utworzyć drugą listę. UżywanieLinkedList
tutaj kończy się jednak znacznie większym zużyciem pamięci niż inne typy kolekcji, a utrata lokalizacji pamięci oznacza, że iteracja będzie zauważalnie wolniejsza, co czyni go nieco gorszym niż aList
.RemoveAll
odpowiednik w Javie.RemoveAll
nie jest dostępnyList
, możesz wykonać algorytm „zagęszczania”, który wyglądałby jak pętla Toma, ale z dwoma indeksami i potrzebą przenoszenia elementów, aby były przechowywane pojedynczo w dół w wewnętrznej tablicy listy. Wydajność wynosi O (n), tak samo jak algorytm TomaLinkedList
. W obu wersjach dominuje czas na obliczenie klucza HashSet dla łańcuchów. To nie jest dobry przykład zastosowaniaLinkedList
.Gdy potrzebujesz wbudowanego dostępu do indeksowania, sortowania (i po tym wyszukiwaniu binarnym) oraz metody „ToArray ()”, powinieneś użyć List.
źródło
Zasadniczo,
List<>
w .NET to opakowanie na tablicę . ALinkedList<>
jest połączoną listą . Pytanie sprowadza się więc do tego, jaka jest różnica między tablicą a listą połączoną i kiedy należy użyć tablicy zamiast listy połączonej. Prawdopodobnie dwa najważniejsze czynniki, które należy podjąć, to:źródło
Jest to dostosowane z zaakceptowanej odpowiedzi Tono Nam korygującej kilka błędnych pomiarów.
Test:
I kod:
Możesz zobaczyć, że wyniki są zgodne z wynikami teoretycznymi, które inni tu udokumentowali. Całkiem jasne -
LinkedList<T>
zyskuje dużo czasu w przypadku wstawek. Nie testowałem usunięcia ze środka listy, ale wynik powinien być taki sam. OczywiścieList<T>
ma inne obszary, w których działa o wiele lepiej, jak losowy dostęp O (1).źródło
Użyj
LinkedList<>
kiedyToken Stream
.Do wszystkiego innego lepiej jest użyć
List<>
.źródło
LinkedListNode<T>
obiekty w kodzie. Jeśli możesz to zrobić, jest to o wiele lepsze niż używanieList<T>
, szczególnie w przypadku bardzo długich list, w których często występują wstawienia / usunięcia.node.Value
kiedy chcesz oryginalny element). Więc przepisujesz algorytm do pracy z węzłami, a nie surowymi wartościami.Zgadzam się z większością powyższych uwag. Zgadzam się również, że Lista w większości przypadków wydaje się bardziej oczywistym wyborem.
Ale chcę tylko dodać, że istnieje wiele przypadków, w których LinkedList jest znacznie lepszym wyborem niż List dla lepszej wydajności.
Mam nadzieję, że ktoś uzna te komentarze za przydatne.
źródło
Tak wiele średnich odpowiedzi tutaj ...
Niektóre implementacje połączonych list używają podstawowych bloków wstępnie przydzielonych węzłów. Jeśli tego nie zrobią, czas stały / liniowy jest mniej istotny, ponieważ wydajność pamięci będzie niska, a wydajność pamięci podręcznej nawet gorsza.
Użyj połączonych list, kiedy
1) Chcesz bezpieczeństwa wątków. Możesz budować lepsze, bezpieczne dla wątków algorytmy. Koszty blokowania będą dominować na liście współbieżnych stylów.
2) Jeśli masz duże kolejki, takie jak struktury i chcesz usunąć lub dodać gdziekolwiek poza końcem przez cały czas. > Listy 100 000 istnieją, ale nie są tak powszechne.
źródło
Zadałem podobne pytanie związane z wydajnością kolekcji LinkedList i odkryłem, że implementacja Deque Stevena Cleary'ego w C # była rozwiązaniem. W przeciwieństwie do kolekcji Queue, Deque umożliwia przenoszenie przedmiotów z przodu iz tyłu. Jest podobny do listy połączonej, ale ma lepszą wydajność.
źródło
Deque
jest „podobne do listy połączonej, ale z lepszą wydajnością” . Proszę określić, że oświadczenie:Deque
jest lepiej niż wydajnośćLinkedList
, dla kodu specyficznego . Po twoim linku widzę, że dwa dni później dowiedziałeś się od Ivana Stoeva, że nie była to nieefektywność LinkedList, ale nieefektywność twojego kodu. (I nawet jeśli byłaby to nieskuteczność LinkedList, nie uzasadniałoby to ogólnego stwierdzenia, że Deque jest bardziej wydajny; tylko w szczególnych przypadkach.)