LinkedList ile karşılaştırıldığında ne zaman bir Liste kullanmalıyım


Yanıtlar:


107

Düzenle

Lütfen bu cevabın yorumlarını okuyun. İnsanlar uygun testler yapmadığımı iddia ediyorlar. Bunun kabul edilmiş bir cevap olmaması gerektiğine katılıyorum. Öğrenirken bazı testler yaptım ve bunları paylaşmak gibi hissettim.

Orijinal cevap ...

İlginç sonuçlar buldum:

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

Bağlantılı liste (3.9 saniye)

        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;

Liste (2.4 saniye)

        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;

Sadece verilere erişseniz bile çok daha yavaştır !! Asla LinkedList kullanma diyorum.




Çok sayıda ekleme yapan başka bir karşılaştırma (listenin ortasına bir öğe eklemeyi planlıyoruz)

Bağlantılı Liste (51 saniye)

        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;

Liste (7.26 saniye)

        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;

Nereye ekleneceğini belirten Bağlantılı Liste (.04 saniye)

        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;

Bu nedenle, yalnızca birkaç öğe eklemeyi planlıyorsanız ve ayrıca bir yere öğeyi nereye eklemeyi planladığınıza ilişkin referansınız varsa bağlantılı bir liste kullanın. Çok fazla öğe eklemeniz gerektiğinden, daha hızlı yapmaz çünkü eklemek istediğiniz konumu aramak zaman alır.


99
LinkedList üzerinden List'in bir yararı vardır (bu .net'e özgüdür): Liste dahili bir dizi tarafından desteklendiğinden, bir bitişik blokta ayrılır. Tahsis edilen blok boyutu 85000 baytı aşarsa, sıkıştırılamaz bir nesil olan Büyük Nesne Yığına tahsis edilecektir. Boyutuna bağlı olarak, bu, hafif bir bellek sızıntısı olan yığın parçalanmasına yol açabilir.
JerKimball

35
Çok fazla öneri (son örnekte yaptığınız gibi) ya da ilk girişi siliyorsanız, arama veya taşıma / kopyalama yapılmadığından bağlantılı bir listenin neredeyse her zaman çok daha hızlı olacağını unutmayın. Bir Liste, yeni öğeyi yerleştirmek için her şeyi bir noktaya taşımak ve bir O (N) işlemi başlatacak şekilde gerektirir.
cHao

6
Neden list.AddLast(a);son iki LinkedList örneğindeki döngü içinde? Bunu, bir önceki list.AddLast(new Temp(1,1,1,1));LinkedList'in yanında olduğu gibi, döngüden önce bir kez yapıyorum , ancak döngülere iki kat daha fazla Temp nesnesi eklediğiniz gibi görünüyor. (Ve kendimi bir test uygulamasıyla tekrar kontrol ettiğimde , yeterince, LinkedList'te iki kat daha fazla.)
Nisan'ta

7
Bu cevabı reddettim. 1) I say never use a linkedList.daha sonraki yayınınızda görüldüğü gibi genel tavsiyeniz hatalı. Düzenlemek isteyebilirsiniz. 2) Zamanlama nedir? Tek bir adımda örnekleme, toplama ve numaralandırma? Çoğunlukla, örnekleme ve numaralandırma, ppl'nin endişe duyduğu şey değildir, bunlar bir defalık adımlardır. Eklerin ve eklemelerin özellikle zamanlanması daha iyi bir fikir verecektir. 3) En önemlisi, bağlantılı bir listeye gerekenden fazlasını ekliyorsunuz. Bu yanlış bir karşılaştırma. Bağlantılı liste hakkında yanlış fikir yayar.
nawfal

47
Üzgünüm, ama bu cevap gerçekten kötü. Lütfen bu yanıtı dinlemeyin. Kısaca Nedeni: Dizi destekli liste uygulamalarının, her eklemede diziyi yeniden boyutlandıracak kadar aptal olduğunu düşünmek tamamen kusurludur. Bağlantılı listeler doğal olarak dizi destekli listelerden daha yavaştır ve her iki uca da eklerken ekler, çünkü dizi destekli listeler bir arabellek kullanır (her iki yönde de açıkça). (Kötü yapılanma) kriterleri tam olarak bunu göstermektedir. Yanıt, bağlantılı listelerin tercih edileceği durumları tamamen kontrol edemez!
mafu

277

Çoğu durumda, List<T>daha yararlıdır. LinkedList<T>listenin ortasındaki öğeleri eklerken / kaldırırken daha az maliyete sahip olurken , listenin sonundaList<T> yalnızca ucuza ekleyebilir / kaldırabilirsiniz .

LinkedList<T>yalnızca sıralı verilere (ileri veya geri) erişiyorsanız en etkilidir - rastgele erişim nispeten pahalıdır, çünkü her seferinde zincirde yürümelidir (bu yüzden neden bir indeksleyiciye sahip değildir). Ancak, a List<T>aslında sadece bir dizi (bir sarıcı ile) rastgele erişim iyidir.

List<T>Ayrıca destek yöntemlerinin çok - biraz Find, ToArrayvb; ancak, bunlar LinkedList<T>.NET 3.5 / C # 3.0 ile uzantı yöntemleri aracılığıyla da kullanılabilir - bu nedenle daha az faktördür.


4
Liste <> ve LinkedList <> 'in bir avantajı, mikroişlemcilerin belleğin önbelleğe alınmasını nasıl gerçekleştirdiğini hiç düşünmediğimdir. Her ne kadar tam olarak anlamamış olsam da, bu blog makalesinin yazarı "referansın yeri" hakkında çok konuşur, bu da bir dizinin geçişini bağlantılı bir listeden geçmekten çok daha hızlı hale getirir , en azından bağlantılı liste bellekte biraz parçalanmışsa . kjellkod.wordpress.com/2012/02/25/…
RenniePet

@RenniePet List dinamik bir dizi ile uygulanır ve diziler bitişik bellek bloklarıdır.
Casey

2
Liste dinamik bir dizi olduğundan, bu nedenle yapıcıdaki bir Listenin kapasitesini önceden biliyorsanız iyi tanımlamak bazen iyi olur.
Cardin Lee JH

All, array, List <T> ve LinkedList <T> 'in C # uygulamasının çok önemli bir durum için bir miktar yetersiz olması mümkün mü: Çok büyük bir listeye ihtiyacınız var, (AddLast) ve sıralı geçiş (bir yönde) tamamen iyi: Ben sürekli blok almak için yeniden boyutlandırma hiçbir dizi istiyorum (her dizi, hatta 20 GB diziler için garanti edilir?), ve önceden boyutunu bilmiyorum, ama önceden bir blok boyutu, örneğin 100 tahmin edebilirim MB, her seferinde önceden ayırmak için. Bu iyi bir uygulama olacaktır. Yoksa dizi / Liste buna benzer ve bir noktayı kaçırdım?
Philm

1
@Pilm, seçtiğiniz blok stratejisi üzerine kendi şiminizi yazdığınız bir senaryo; List<T>ve T[]çok tıknaz olduğu için başarısız olacak (hepsi bir levha), LinkedList<T>çok parçalı olmasını bekleyecek (eleman başına levha).
Marc Gravell

212

Bağlantılı bir listeyi liste olarak düşünmek biraz yanıltıcı olabilir. Daha çok bir zincir gibi. Aslında, .NET'te LinkedList<T>bile uygulamaz IList<T>. Bağlantılı bir listede gerçek bir dizin kavramı yoktur, ancak görünse de. Sınıfta sağlanan yöntemlerin hiçbiri dizinleri kabul etmez.

Bağlantılı listeler tek başlarına veya iki kez bağlanabilir. Bu, zincirdeki her bir elemanın sadece bir sonrakine (tek başına bağlı) veya hem önceki / sonraki elemanlara (iki kez bağlı) bir bağlantıya sahip olup olmadığını ifade eder. LinkedList<T>iki kez bağlantılıdır.

Dahili olarak List<T>bir dizi tarafından desteklenir. Bu, bellekte çok kompakt bir gösterim sağlar. Tersine, LinkedList<T>birbirini izleyen elemanlar arasındaki çift yönlü bağlantıları saklamak için ek bellek içerir. Bu nedenle, a'nın bellek alanı LinkedList<T>genellikle olduğundan daha büyük olacaktır List<T>( List<T>ekleme işlemleri sırasında performansı artırmak için kullanılmayan dahili dizi öğelerine sahip olabilecek uyarı ile ).

Farklı performans özellikleri de vardır:

ekleme

  • LinkedList<T>.AddLast(item) sabit zaman
  • List<T>.Add(item) itfa edilmiş sabit zamanlı, doğrusal en kötü durum

Başına Ekle

  • LinkedList<T>.AddFirst(item) sabit zaman
  • List<T>.Insert(0, item) doğrusal zaman

sokma

  • LinkedList<T>.AddBefore(node, item) sabit zaman
  • LinkedList<T>.AddAfter(node, item) sabit zaman
  • List<T>.Insert(index, item) doğrusal zaman

uzaklaştırma

  • LinkedList<T>.Remove(item) doğrusal zaman
  • LinkedList<T>.Remove(node) sabit zaman
  • List<T>.Remove(item) doğrusal zaman
  • List<T>.RemoveAt(index) doğrusal zaman

Miktar

  • LinkedList<T>.Count sabit zaman
  • List<T>.Count sabit zaman

İçeren

  • LinkedList<T>.Contains(item) doğrusal zaman
  • List<T>.Contains(item) doğrusal zaman

Açık

  • LinkedList<T>.Clear() doğrusal zaman
  • List<T>.Clear() doğrusal zaman

Gördüğünüz gibi, çoğunlukla eşdeğerler. Uygulamada, API'sini LinkedList<T>kullanmak daha zahmetlidir ve dahili ihtiyaçlarının ayrıntıları kodunuza dökülür.

Ancak, bir listeden çok sayıda ekleme / kaldırma işlemi yapmanız gerekiyorsa, sabit bir süre sunar. List<T>ekleme / çıkarma işleminden sonra listedeki fazladan öğelerin karıştırılması gerektiği için doğrusal zaman sunar.


2
Sayısal bağlantılı listesi sabit mi? Bunun doğrusal olacağını mı düşündüm?
Iain Ballard

10
@Iain, sayım her iki liste sınıfında önbelleğe alınır.
Drew Noakes

3
"Liste <T> .Add (öğe) logaritmik zaman" yazdınız, ancak liste kapasitesi yeni öğeyi saklayabilirse aslında "Sabit" ve listede yeterli alan ve yeni yoksa "Doğrusal" yeniden tahsis edilecek.
aStranger

@aStranger, elbette haklısın. Yukarıda ne düşündüğümden emin değilim - belki de normal amortisman vakası logaritmiktir, ki bu değildir. Aslında itfa edilmiş zaman sabittir. Basit bir karşılaştırma yapmak için operasyonların en iyi / en kötü durumuna girmedim. Ancak ekleme işleminin bu ayrıntıyı sağlayacak kadar önemli olduğunu düşünüyorum. Cevabı düzenleyecek. Teşekkürler.
Drew Noakes

1
@Philm, muhtemelen yeni bir soru başlatmalısınız ve inşa edildikten sonra bu veri yapısını nasıl kullanacağınızı söylemiyorsunuz, ancak bir milyon satırdan bahsediyorsanız, bir çeşit melez (bağlantılı liste yığın parçalarını veya benzerlerini) yığın parçalanmasını azaltmak, bellek yükünü azaltmak ve LOH üzerinde tek bir büyük nesneden kaçınmak için.
Drew Noakes

118

Bağlantılı listeler, liste üyesinin çok hızlı eklenmesi veya silinmesini sağlar. Bağlantılı listedeki her üye, i konumunda bir üye eklemek için listedeki bir sonraki üyeye bir işaretçi içerir:

  • üye i-1'deki işaretçiyi yeni üyeye işaret edecek şekilde güncelle
  • yeni üyedeki işaretçiyi i üyesine işaret edecek şekilde ayarlama

Bağlantılı bir listenin dezavantajı, rasgele erişimin mümkün olmamasıdır. Bir üyeye erişmek için, istenen üye bulunana kadar listeyi dolaşmak gerekir.


6
Bağlantılı listelerin yukarıda ve önceki düğüme başvuran LinkedListNode aracılığıyla örtülü saklanan öğe başına bir ek yük olduğunu eklerdim. Bunun, dizi tabanlı bir listenin aksine listeyi saklamak için bitişik bir bellek bloğu olması gerekir.
paulecoyote

3
Bitişik bir bellek bloğu genellikle göz önüne alınmıyor mu?
Jonathan Allen

7
Evet, rasgele erişim performansı ve bellek tüketimi için bitişik bir blok tercih edilir, ancak boyutu düzenli olarak değiştirmesi gereken koleksiyonlar için Array gibi bir yapının genellikle yeni bir konuma kopyalanması gerekirken, bağlantılı bir listenin yalnızca yeni eklenen / silinen düğümler.
jpierson

6
Şimdiye kadar çok büyük diziler veya listelerle (bir liste sadece bir dizi sarar) çalışmak zorunda kaldıysanız, makinenizde bol miktarda bellek var gibi görünse bile bellek sorunlarıyla karşılaşmaya başlayacaksınız. Liste, temel dizisinde yeni alan ayırdığında bir katlama stratejisi kullanır. Böylece, dolu bir 1000000 elemnt dizisi 2000000 elemanlı yeni bir diziye kopyalanacaktır. Bu yeni dizinin, onu tutacak kadar büyük bitişik bir bellek alanında oluşturulması gerekir.
Andrew

1
Tüm yaptığım ekleme ve kaldırma ve tek tek döngü burada özel bir dava vardı ... burada bağlantılı liste normal listesinden çok daha üstündü ..
Peter

26

Önceki cevabım yeterince doğru değildi. Gerçekten korkunçtu: D Ama şimdi çok daha yararlı ve doğru cevap gönderebilirim.


Bazı testler yaptım. Kaynağını aşağıdaki bağlantıdan bulabilir ve kendi ortamınızda yeniden kontrol edebilirsiniz: https://github.com/ukushu/DataStructuresTestsAndOther.git

Kısa sonuçlar:

  • Dizi kullanmanız gerekir:

    • Mümkün olduğunca sık. Hızlıdır ve aynı miktarda bilgi için en küçük RAM aralığını alır.
    • Tam olarak gereken hücre sayısını biliyorsanız
    • Veri <85000 b dizisine kaydedilmişse (tamsayı verileri için 85000/32 = 2656 öğe)
    • Gerekirse yüksek Rasgele Erişim hızı
  • Liste kullanmanız gerekir:

    • Listenin sonuna hücre eklemek gerekirse (genellikle)
    • Listenin başına / ortasına hücre eklemek gerekirse (OFTEN DEĞİL)
    • Veri <85000 b dizisine kaydedilmişse (tamsayı verileri için 85000/32 = 2656 öğe)
    • Gerekirse yüksek Rasgele Erişim hızı
  • LinkedList şunları kullanmalıdır:

    • Listenin başına / ortasına / sonuna hücre eklemek gerekirse (genellikle)
    • Gerekirse yalnızca sıralı erişim (ileri / geri)
    • BÜYÜK öğeleri kaydetmeniz gerekiyorsa, ancak öğe sayısı düşükse.
    • Bağlantılar için ek bellek kullandığından, büyük miktarda öğe için kullanmayın.

Daha fazla detay:

введите сюда описание изображения Bilmek ilginç:

  1. LinkedList<T>dahili olarak .NET'te bir Liste değildir. Uygulanmıyor bile IList<T>. İşte bu yüzden endekslerle ilgili olmayan indeksler ve yöntemler var.

  2. LinkedList<T>düğüm-işaretçi tabanlı bir koleksiyon. .NET'te çift bağlantılı uygulamada. Bu, önceki / sonraki öğelerin geçerli öğeye bağlantısı olduğu anlamına gelir. Ve veriler parçalanır - farklı liste nesneleri farklı RAM yerlerine yerleştirilebilir. Ayrıca LinkedList<T>, için List<T>veya Array'dan daha fazla bellek kullanılacak .

  3. List<T>Net, Java'nın alternatifidir ArrayList<T>. Bu, bunun dizi sarıcı olduğu anlamına gelir. Bu yüzden bellekte bitişik bir veri bloğu olarak ayrılır. Tahsis edilen veri boyutu 85000 baytı aşarsa, Büyük Nesne Yığını'na taşınır. Boyutuna bağlı olarak, bu yığın parçalanmasına (hafif bir bellek sızıntısı formu) yol açabilir. Ancak aynı zamanda <85000 bayt boyutundaysa - bu, bellekte çok kompakt ve hızlı erişim temsili sağlar.

  4. Rastgele erişim performansı ve bellek tüketimi için tek bitişik blok tercih edilir, ancak boyutu düzenli olarak değiştirmesi gereken koleksiyonlar için Array gibi bir yapının genellikle yeni bir konuma kopyalanması gerekir, oysa bağlantılı bir listenin yalnızca yeni eklenenler için belleği yönetmesi gerekir / silinmiş düğümler.


1
Soru: "Dizi <veya> 85.000 baytta kaydedilmiş veriler" ile dizi / liste başına veri demek ELEMENT nedir? Tüm dizinin veri boyutunu kastettiğiniz anlaşılabilir.
Philm

Sıralı olarak bellekte bulunan dizi elemanları. Dizi başına. Tablodaki hatayı biliyorum, sonra düzeltirim :) (Umarım ....)
Andrew

Ekler sırasında listelerin yavaş olmasıyla, bir listede çok fazla geri dönüş (çok sayıda ek / silme) varsa, silinmiş alan tarafından tutulan bellek doludur ve eğer öyleyse, bu "yeniden" ekleri daha hızlı hale getirir mi?
Rob

18

List ve LinkedList arasındaki fark temel uygulamalarında yatmaktadır. Liste, dizi tabanlı koleksiyondur (ArrayList). LinkedList, düğüm işaretçisi tabanlı bir koleksiyon (LinkedListNode). API düzeyinde kullanımda, ikisi de hemen hemen aynıdır, çünkü her ikisi de ICollection, IEnumerable gibi aynı arabirim kümesini uygular.

Temel fark performans önemli olduğunda ortaya çıkar. Örneğin, ağır "INSERT" işlemi olan listeyi uyguluyorsanız, LinkedList List'ten daha iyi performans gösterir. LinkedList bunu O (1) sürede yapabildiğinden, List'in temeldeki dizinin boyutunu genişletmesi gerekebilir. Daha fazla bilgi / ayrıntı için LinkedList ve dizi veri yapıları arasındaki algoritmik farkı okumak isteyebilirsiniz.http://en.wikipedia.org/wiki/Linked_list ve Dizi

Umarım bu yardım,


4
<T> listesi dizi (T []) tabanlıdır, ArrayList tabanlıdır. Yeniden yerleştirin: dizi yeniden boyutlandırma sorun değildir (iki katına çıkarma algoritması çoğu zaman bunu yapmak zorunda olmadığı anlamına gelir): sorun, önce tüm mevcut verileri bloke kopyalaması gerektiğidir, bu da biraz zaman alır saati.
Marc Gravell

2
@Marc, 'çiftleme algoritması' onu sadece O (logN) yapar, ama yine de O'dan daha kötüdür (1)
Ilya Ryzhenkov

2
Demek istediğim, acıya neden olan yeniden boyutlandırma değil - bu küstahlıktı. En kötü durumda, her seferinde ilk (sıfırıncı) elemanı ekliyorsak, blit her seferinde her şeyi hareket ettirmek zorundadır.
Marc Gravell

@IlyaRyzhenkov - Addher zaman mevcut dizinin sonunda olan durumu düşünüyorsunuz . ListO (1) olmasa bile "yeterince iyi" dir. Sonunda olmayan birçok Adds gerekiyorsa ciddi sorun oluşur . Marc, her eklediğinizde (yalnızca yeniden boyutlandırma gerektiğinde değil) mevcut verilerin taşınması gerektiğinin daha önemli bir performans maliyeti olduğuna dikkat çekiyor . List
ToolmakerSteve

Sorun Teorik Büyük O notasyonlarının tüm hikayeyi anlatmamasıdır. Bilgisayar bilimlerinde herkesin umurunda olduğu, ancak bununla ilgilenmek için gerçek dünyada çok daha fazlası var.
MattE

11

Bağlantılı listelerin dizilere göre birincil avantajı, bağlantıların öğeleri verimli bir şekilde yeniden düzenleme olanağı sağlamasıdır. Sedgewick, s. 91


1
IMO bu cevap olmalı. LinkedList, garantili bir sipariş önemli olduğunda kullanılır.
RBaarda

1
@RBaarda: Kabul etmiyorum. Bu, bahsettiğimiz seviyeye bağlıdır. Algoritmik seviye, makine uygulama seviyesinden farklıdır. Hız değerlendirmesi için ikincisine de ihtiyacınız var. Belirtildiği gibi, diziler bir kısıtlama olan belleğin "tek bir parçası" olarak uygulanır, çünkü bu özellikle çok büyük dizilerle yeniden boyutlandırmaya ve bellek yeniden düzenlenmesine yol açabilir. Bir süre düşündükten sonra, özel bir kendi veri yapısı, bağlantılı diziler listesi, doğrusal doldurma hızı ve çok büyük veri yapılarına erişim üzerinde daha iyi kontrol sağlamak için bir fikir olacaktır.
Philm

1
@Philm - Yorumunuzu iptal ettim, ancak farklı bir gereksinimi tanımladığınızı belirtmek isterim. Yanıtın söylediği şey, bağlantılı listenin, öğelerin yeniden düzenlenmesini içeren algoritmalar için performans avantajına sahip olmasıdır . Bu göz önüne alındığında, RBaarda'nın yorumunu, verilen bir siparişi sürekli olarak korurken öğe ekleme / silme ihtiyacına atıfta bulunarak yorumluyorum (sıralama ölçütleri). Yani sadece "doğrusal dolgu" değil. Bu göz önüne alındığında, Liste kaybedilir, çünkü endeksler işe yaramaz (kuyruk ucuna her yere bir öğe eklediğinizde değiştirin).
Araç ÜreticisiSteve

4

LinkedList'i kullanmak için yaygın bir durum şöyledir:

Diyelim ki 100.000 gibi büyük boyutlu dizelerin listesinden birçok dizeyi kaldırmak istediğinizi varsayalım. Kaldırılacak dizeler HashSet dic'de aranabilir ve dizeler listesinin kaldırılacak 30.000 ila 60.000 arasında dizeyi içerdiğine inanılır.

Peki 100.000 Dizeyi saklamak için en iyi Liste türü nedir? Cevap LinkedList. Bir ArrayList içinde saklanırlarsa, o zaman tekrarlar ve milyarlarca işlem alabilen eşleşen Dizeleri kaldırırlar, ancak bir yineleyici ve remove () yöntemini kullanarak yaklaşık 100.000 işlem alır.

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
Sadece kullanabilirsiniz RemoveAllbir öğe kaldırma Listetrafında öğeleri bir sürü hareket etmeden veya kullanmak Whereikinci bir liste oluşturmak için LINQ dan. Bir kullanılması LinkedListancak burada tüketen biter dramatik biraz daha kötü bir daha yapma, bu önemli ölçüde daha yavaş yinelemenizi olacağı diğer koleksiyonların türleri ve hafıza mevkiinde araçlarının kaybı daha fazla bellek List.
14'te Servis

@Servy, @ Tom'un cevabının Java kullandığını unutmayın. RemoveAllJava'da bir eşdeğeri olup olmadığından emin değilim .
Arturo Torres Sánchez

3
@ ArturoTorresSánchez Soru, özellikle bunun .NET ile ilgili olduğunu belirtiyor, bu yüzden yanıtı daha az uygun hale getiriyor.
Servy

@Servy, o zaman en başından bahsetmeliydin.
Arturo Torres Sánchez

Kullanılamıyorsa RemoveAll, ListTom'un döngüsüne benzeyen bir "sıkıştırma" algoritması yapabilirsiniz, ancak iki dizin ve öğelerin listenin dahili dizisinde her seferinde bir tane tutulması için taşıma ihtiyacı vardır. Verimlilik, Tom'un algoritmasıyla aynı olan O (n) 'dir LinkedList. Her iki sürümde de, dizeler için HashSet anahtarını hesaplama zamanı hakimdir. Bu ne zaman kullanılacağına iyi bir örnek değildir LinkedList.
ToolmakerSteve

2

Yerleşik dizinli erişim, sıralama (ve bu ikili aramanın ardından) ve "ToArray ()" yöntemine ihtiyacınız olduğunda, List komutunu kullanmalısınız.


2

Temel olarak, List<>.NET'te bir dizi üzerinde bir sarıcıdır . A LinkedList<> bağlantılı bir listedir . Dolayısıyla, bir dizi ile bağlantılı bir liste arasındaki fark nedir ve bağlantılı bir liste yerine bir dizi ne zaman kullanılmalıdır. Muhtemelen, kararınızda kullanılacak en önemli iki faktör aşağıdakilere inecektir:

  • Eklenenler / kaldırmalar koleksiyondaki son öğede olmadığı sürece, bağlantılı listeler daha iyi ekleme / kaldırma performansına sahiptir. Bunun nedeni, bir dizinin ekleme / kaldırma noktasından sonra gelen tüm diğer öğeleri kaydırması gerektiğidir. Ancak ekleme / çıkarma listenin arka ucundaysa, bu kaydırma gerekli değildir (ancak kapasitesi aşılırsa dizinin yeniden boyutlandırılması gerekebilir).
  • Diziler daha iyi erişim yeteneklerine sahiptir. Diziler doğrudan (sabit zamanda) dizine eklenebilir. Bağlantılı listeler arasında geçiş yapılmalıdır (doğrusal zaman).

1

Bu Tono Nam'dan uyarlanmıştır birkaç yanlış ölçümü düzelten kabul edilen cevabından .

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

Ve kod:

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

Sonuçların, diğerlerinin burada belgelediği teorik performansa uygun olduğunu görebilirsiniz. Oldukça açık -LinkedList<T> yerleştirme durumunda büyük zaman kazanır. Listenin ortasından kaldırılmak üzere test etmedim, ancak sonuç aynı olmalı. Tabii ki List<T>O (1) rastgele erişim gibi daha iyi performans gösteren diğer alanları var.


0

LinkedList<>Ne zaman kullanın

  1. Sel kapısından kaç nesnenin geldiğini bilmiyorsunuz. Örneğin,Token Stream ,.
  2. SADECE uçlarında \ insert silmek istediğinizde.

Diğer her şey için, kullanmak daha iyidir List<>.


6
2. noktanın neden anlamlı olduğunu anlamıyorum. Bağlantılı listeler, tüm liste boyunca birçok ekleme / silme işlemi yaparken mükemmeldir.
Drew Noakes

LinkedLists'in Dizin tabanlı olmaması nedeniyle, gerçekten tüm listeyi O (n) cezası alan ekleme veya silme için taramanız gerekir. Öte yandan liste <> listesinin yeniden boyutlandırılmasından muzdarip olmakla birlikte, yine de IMO, LinkedLists ile karşılaştırıldığında daha iyi bir seçenektir.
Antony Thomas

1
LinkedListNode<T>Kodunuzdaki nesneleri izlerseniz listeyi ekleme / silme işlemleri için taramanız gerekmez . Bunu yapabiliyorsanız List<T>, özellikle eklerin / çıkarmaların sık olduğu çok uzun listeler için kullanmaktan çok daha iyidir .
Drew Noakes

Bir hashtable mı demek istiyorsun? Bu durumda, her bilgisayar programcının sorun alanına dayalı bir seçim yapması gereken tipik uzay \ zaman geliri olur :) Ama evet, bu daha hızlı olur.
Antony Thomas

1
@AntonyThomas - Hayır, elemanlara yapılan referansları iletmek yerine düğümlere referanslar iletmek anlamına gelir . Eğer tek şey bir ise eleman , daha sonra her iki aramak zorunda çünkü Listesi ve LinkedList, kötü performansa sahip. "Ancak Liste ile sadece bir dizine geçebilirim" diye düşünüyorsanız: bu yalnızca Listenin ortasına yeni bir öğe eklemediğinizde geçerlidir. LinkedList, bu sınırlama yoktur eğer bir tutunmaya düğüm (ve kullanımı özgün eleman istedikleri zaman). Yani algoritmayı ham değerlerle değil, düğümlerle çalışacak şekilde yeniden yazarsınız. node.Value
ToolmakerSteve

0

Yukarıda belirtilen hususların çoğuna katılıyorum. Listenin de çoğu durumda daha bariz bir seçenek gibi göründüğüne katılıyorum.

Ancak, sadece daha iyi verimlilik için LinkedList listesi çok daha iyi bir seçim olduğu birçok örnek olduğunu eklemek istiyorum.

  1. Öğeler arasında geçiş yaptığınızı ve çok sayıda ekleme / silme gerçekleştirmek istediğinizi varsayalım; LinkedList bunu lineer O (n) zamanda, List ise ikinci dereceden O (n ^ 2) zamanda yapar.
  2. Büyük nesnelere tekrar tekrar erişmek istediğinizi varsayalım, LinkedList çok daha kullanışlı hale geliyor.
  3. Deque () ve queue (), LinkedList kullanılarak daha iyi uygulanır.
  4. LinkedList'in boyutunu artırmak, daha büyük ve daha büyük nesnelerle uğraşırken çok daha kolay ve daha iyidir.

Umarım birisi bu yorumları faydalı bulur.


Bu tavsiyenin Java için değil .NET için olduğunu unutmayın. Java'nın bağlantılı liste uygulamasında "geçerli düğüm" kavramına sahip değilsiniz, bu nedenle her ek için listeyi dolaşmanız gerekir.
Jonathan Allen

Bu cevap sadece kısmen doğrudur: 2) elemanlar büyükse, eleman türünü bir Yapı değil Sınıf olarak yapın, böylece Liste basitçe bir referansa sahiptir. Ardından eleman boyutu önemsiz hale gelir. 3) Listeyi başlangıçta ekleme veya kaldırma yapmak yerine "dairesel tampon" olarak kullanırsanız , Deque ve kuyruk bir Listede verimli bir şekilde yapılabilir . StephenCleary'nin Deque . 4) kısmen doğru: birçok nesne, LL yanlısı büyük bitişik bellek gerekmez; olumsuz, düğüm işaretçileri için fazladan bellektir.
ToolmakerSteve

-2

Burada çok fazla ortalama cevap var ...

Bazı bağlantılı liste uygulamaları, önceden atanmış düğümlerin altında yatan blokları kullanır. Bunu sabit süreden / doğrusal süreden daha fazla yapmazlarsa, bellek performansı zayıf olacağından ve önbellek performansı daha da kötü olacağından daha az önemlidir.

Aşağıdaki durumlarda bağlantılı listeleri kullan

1) İplik güvenliği istiyorsunuz. Daha iyi iplik güvenli algos oluşturabilirsiniz. Kilitleme maliyetleri eşzamanlı bir stil listesine hakim olacaktır.

2) Yapılar gibi büyük bir kuyruğunuz varsa ve her zaman sondan başka bir yeri kaldırmak veya eklemek istiyorsanız. 100.000'den fazla liste var ancak bu kadar yaygın değil.


3
Bu soru, genel olarak bağlantılı listeler değil, iki C # uygulamasıyla ilgiliydi.
Jonathan Allen

Her dilde aynı
user1496062

-2

LinkedList koleksiyonunun performansı ile ilgili benzer bir soru sordum ve Steven Cleary'nin Deque'nin C # uygulamasının bir çözüm olduğunu keşfetti . Kuyruk koleksiyonundan farklı olarak, Deque öğelerin ön ve arkada hareket ettirilmesine izin verir. Bağlantılı listeye benzer, ancak performansı artırılmıştır.


1
İfadenizi Re Dequeolduğu "bağlantılı listeye benzer fakat gelişmiş performans ile" . Bu ifadeyi nitelemek edin: Dequedaha iyi performans LinkedList, özel kodu için . Bağlantınızı takiben, iki gün sonra Ivan Stoev'den bunun LinkedList'in verimsizliği değil, kodunuzdaki verimsizlik olduğunu öğrendiğinizi görüyorum. (Bu LinkedList bir verimsizlik olmuştu bile, o deque daha verimli olduğuna dair genel bir açıklama haklılaştırmaz, sadece özel durumlarda.)
ToolmakerSteve
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.