Bağlantılı listeler hangi koşullar altında faydalıdır?


110

Çoğu zaman insanların bağlantılı listeleri kullanmaya çalıştığını görüyorum, bu bana zayıf (veya çok zayıf) bir seçim gibi görünüyor. Belki de bağlantılı bir listenin veri yapısı için iyi bir seçim olduğu veya olmadığı durumları keşfetmek faydalı olabilir.

İdeal olarak, yanıtlar, bir veri yapısının seçilmesinde kullanılacak kriterler ve belirli koşullar altında hangi veri yapılarının en iyi şekilde çalışacağı konusunda açıklanacaktır.

Düzenleme: Söylemeliyim, sadece sayıdan değil, cevapların kalitesinden oldukça etkilendim. Sadece birini kabul edebilirim, ancak iki veya üç tane daha var, orada biraz daha iyi bir şey olmasaydı kabul etmeye değer olduğunu söylemeliyim. Yalnızca bir çift (özellikle de kabul ettiğim kişi) bağlantılı bir listenin gerçek bir avantaj sağladığı durumlara işaret etti. Sanırım Steve Jessop, sadece bir değil, üç farklı cevapla geldiği için bir tür onurlu sözü hak ediyor, bunların hepsini oldukça etkileyici buldum. Elbette, yanıt olarak değil, yalnızca yorum olarak yayınlanmış olsa da, Neil'in blog yazısı da okumaya değer olduğunu düşünüyorum - yalnızca bilgilendirici değil, aynı zamanda oldukça eğlenceli.


34
İkinci paragrafınızın cevabı yaklaşık bir dönem sürer.
Seva Alekseyev

2
Benim fikrim için, punchlet.wordpress.com/2009/12/27/letter-the-fourth adresini ziyaret edin . Ve bu bir anket gibi göründüğü için muhtemelen CW olmalı.

1
@Neil, güzel, yine de CS Lewis'in onaylayacağından şüpheliyim.
Tom

@Neil: Sanırım bir tür anket. Çoğunlukla, en azından mantıklı olarak satın alabileceğim bir temeli olan bir cevabı herhangi birinin bulup bulamayacağını görme girişimidir. @Seva: evet, tekrar okudum, son cümleyi başlangıçta istediğimden biraz daha genel yaptım.
Jerry Coffin

2
@Yar İnsanlar (ben de dahil olmak üzere üzgünüm) FORTRAN IV (işaretçi kavramı olmayan) gibi dillerde işaretçiler olmadan bağlantılı listeleri ağaç gibi kullanırlardı. "Gerçek" bellek yerine diziler kullandınız.

Yanıtlar:


40

Eşzamanlı veri yapıları için yararlı olabilirler. (Şimdi aşağıda eşzamanlı olmayan gerçek dünya kullanım örneği var - @Neil FORTRAN'dan bahsetmemiş olsaydı bu orada olmazdı . ;-)

Örneğin, ConcurrentDictionary<TKey, TValue>.NET 4.0 RC'de, aynı gruba hash olan öğeleri zincirlemek için bağlantılı listeleri kullanın.

Temel veri yapısı ConcurrentStack<T>da bağlantılı bir listedir.

ConcurrentStack<T>yeni İş Parçacığı Havuzu için temel teşkil eden veri yapılarından biridir ( temelde yığınlar olarak uygulanan yerel "kuyruklar" ile). (Diğer ana destekleyici yapıdır ConcurrentQueue<T>.)

Yeni İş Parçacığı Havuzu, yeni Görev Paralel Kitaplığı'nın iş planlaması için temel sağlar .

Dolayısıyla, kesinlikle faydalı olabilirler - bağlantılı bir liste şu anda en az bir büyük yeni teknolojinin ana destekleyici yapılarından biri olarak hizmet ediyor.

(Tek bağlantılı bir liste, bu durumlarda zorunlu bir kilitsiz seçim yapar - ancak beklemesiz değil - çünkü ana işlemler tek bir CAS (+ yeniden deneme) ile gerçekleştirilebilir. Modern bir GC-d ortamında - örneğin Java ve .NET - ABA sorunu kolayca önlenebilir.Yeni oluşturulmuş düğümlere eklediğiniz öğeleri sarın ve bu düğümleri yeniden kullanmayın - bırakın GC işini yapsın. ABA sorunu sayfası ayrıca bir kilidin uygulanmasını sağlar- ücretsiz yığın - öğeleri tutan bir (GC-ed) Düğümüyle .Net (ve Java) 'da çalışır.)

Düzenleme : @Neil: Aslında, FORTRAN hakkında bahsettiğiniz şey, aynı tür bağlantılı listelerin muhtemelen .NET'te en çok kullanılan ve kötüye kullanılan veri yapısında bulunabileceğini hatırlattı: düz .NET jenerik Dictionary<TKey, TValue>.

Bir değil, birçok bağlantılı liste bir dizide saklanır.

  • Ekler / silmeler üzerinde çok sayıda küçük (de) tahsis yapmaktan kaçınır.
  • Karma tablonun ilk yüklemesi oldukça hızlıdır, çünkü dizi sırayla doldurulur (CPU önbelleği ile çok iyi oynar).
  • Zincirleme hash tablosunun bellek açısından pahalı olduğundan bahsetmiyorum bile - ve bu "numara" x64'te "işaretçi boyutlarını" yarıya indiriyor.

Esasen, birçok bağlantılı liste bir dizide saklanır. (kullanılan her bir kova için bir tane.) Yeniden kullanılabilir düğümlerin ücretsiz bir listesi, aralarında "iç içe geçmiştir" (silme varsa). Yeniden işlemin başında / başında bir dizi tahsis edilir ve zincirlerin düğümleri içinde tutulur. Ayrıca, silme işlemlerini izleyen, diziye bir dizin olan serbest bir işaretçi de vardır . ;-) Öyleyse - ister inan ister inanma - FORTRAN tekniği hala yaşıyor. (... ve başka hiçbir yerde, en sık kullanılan .NET veri yapılarından biri ;-).


2
Kaçırmış olmanız durumunda, Neil'in yorumu şu şekildedir: "İnsanlar (ben de dahil olmak üzere üzgünüm) FORTRAN IV gibi dillerde (işaretçi kavramı olmayan) bağlantılı listeleri işaretçiler olmadan uygularlardı, tıpkı ağaçlarda olduğu gibi "Gerçek" bellek "yerine diziler kullandınız.
Andras Vass

DictionaryNET'te kaydetme durumunda önemli ölçüde daha fazla "bir dizideki bağlantılı listeler" yaklaşımını eklemeliyim : aksi takdirde her düğüm, yığın üzerinde ayrı bir nesne gerektirecektir - ve yığın üzerinde tahsis edilen her nesnenin bir miktar ek yükü vardır. ( en.csharp-online.net/Common_Type_System%E2%80%94Object_Layout )
Andras Vass

C ++ 'ın varsayılan std::listdeğerinin kilitleri olmayan çok iş parçacıklı bir bağlamda güvenli olmadığını bilmek de iyidir .
Mooing Duck

50

Bağlantılı listeler, rastgele (derleme zamanında bilinmeyen) uzunluk listesinde çok fazla ekleme ve çıkarma yapmanız gerektiğinde, ancak çok fazla arama yapmanız gerektiğinde çok kullanışlıdır.

Listeleri bölme ve birleştirme (çift yönlü bağlantılı) çok etkilidir.

Bağlantılı listeleri de birleştirebilirsiniz - örneğin, ağaç yapıları yatay bağlantılı listeleri (kardeşler) birbirine bağlayan "dikey" bağlantılı listeler (üst / alt ilişkiler) olarak uygulanabilir.

Bu amaçlar için dizi tabanlı bir liste kullanmak ciddi sınırlamalara sahiptir:

  • Yeni bir öğe eklemek, dizinin yeniden tahsis edilmesi gerektiği anlamına gelir (veya gelecekteki büyümeye izin vermeniz ve yeniden tahsislerin sayısını azaltmanız için gerekenden daha fazla alan ayırmanız gerekir)
  • Öğeleri çıkarmak, boşa boşa alan bırakır veya yeniden tahsisi gerektirir
  • Son haricinde herhangi bir yere öğe eklemek (muhtemelen yeniden tahsis etmek ve) çok sayıda veriyi bir konum yukarı kopyalamayı içerir

5
Sorusu için azaltır Ne zaman yapmak sıralı listedeki pek çok aramalarını bir dizinin ortasında eklentiler ve kaldırma bir sürü yapmak gerekir, ancak? Bağlantılı bir listeyi gezmek, tipik olarak bir diziyi kopyalamak kadar veya daha pahalıdır, bu nedenle dizilerdeki öğeleri kaldırmak ve eklemek hakkında söylediğiniz her şey, listelerde rastgele erişim için de aynı derecede kötüdür. LRU önbelleği aklıma gelen bir örnek, ortada çok şey kaldırmanız gerekiyor, ancak listeyi asla yürümenize gerek yok.
Steve Jessop

2
Bir listeye ekleme, eklediğiniz her öğe için bellek ayırmayı içerir. Bu, çok pahalı olacak bir sistem çağrısı içerebilir. Bir diziye eklemek, yalnızca dizinin büyütülmesi gerekiyorsa böyle bir çağrı gerektirir. Aslında, çoğu dilde (tam olarak bu nedenlerden dolayı) dizi tercih edilen veri yapısıdır ve listeler neredeyse hiç kullanılmaz.

1
Varsayalım ki? Tahsisatın şaşırtıcı derecede hızlı olduğu açıktır - genellikle nesne boyutunun bir işaretçiye eklenmesini gerektirir. GC için bu toplam ek yük düşük mü? En son gerçek bir uygulamada ölçmeyi denediğimde, kilit nokta Java'nın tüm işi işlemci zaten boştayken yapıyor olmasıydı, bu nedenle doğal olarak görünür performansı fazla etkilemiyordu. Meşgul bir CPU kıyaslamasında Java'yı alt üst etmek ve çok kötü en kötü durum tahsis süresi elde etmek kolaydı. Yine de bu yıllar önceydi ve kuşak çöp toplama, o zamandan beri GC'nin toplam maliyetini önemli ölçüde düşürdü.
Steve Jessop

1
@Steve: Ayırmanın listeler ve diziler arasında "aynı" olması konusunda yanılıyorsunuz. Bir liste için her bellek ayırmanız gerektiğinde, basitçe küçük bir blok - O (1) ayırırsınız. Bir dizi için, tüm liste için yeterince büyük yeni bir blok ayırmanız ve ardından tüm listeyi - O (n) kopyalamanız gerekir. Bir listedeki bilinen bir konuma eklemek için sabit sayıda işaretçi - O (1) güncellersiniz, ancak bir diziye eklemek ve daha sonraki öğeleri yerleştirmeye yer açmak için bir konuma kopyalamak için - O (n). Bu nedenle dizilerin LL'lerden çok daha az verimli olduğu birçok durum vardır.
Jason Williams

1
@ Jerry: Anlıyorum. Demek istediğim, diziyi yeniden tahsis etmenin maliyetinin büyük bir kısmının bellek ayırmak olmadığı , tüm dizi içeriğini yeni belleğe kopyalama ihtiyacı olduğudur. Bir dizinin 0. maddesine eklemek için tüm dizi içeriğini bellekte bir pozisyon yukarı kopyalamanız gerekir . Dizilerin kötü olduğunu söylemiyorum; sadece rastgele erişimin gerekli olmadığı ve LL'lerin gerçekten sabit zamanlı ekleme / silme / yeniden bağlantısının tercih edildiği durumlar vardır.
Jason Williams

20

Bağlantılı listeler çok esnektir: Bir işaretçinin değiştirilmesiyle, aynı işlemin bir dizi listesinde çok verimsiz olacağı büyük bir değişiklik yapabilirsiniz.


Neden bir set veya harita değil de bir listeyi kullanmanın nedenini motive etmek mümkün olabilir mi?
patrik

14

Diziler, Bağlantılı Listelerin genellikle karşılaştırıldığı veri yapılarıdır.

Normalde bağlantılı listeler, listenin kendisinde çok fazla değişiklik yapmanız gerektiğinde, diziler doğrudan öğe erişiminde listelerden daha iyi performans gösterdiğinde kullanışlıdır.

Göreceli işlem maliyeti (n = liste / dizi uzunluğu) ile karşılaştırmalı olarak listeler ve dizilerde gerçekleştirilebilecek işlemlerin listesi:

  • Bir eleman eklemek:
    • Listelerde sadece yeni eleman ve yönlendirme işaretçileri için bellek ayırmanız gerekir. O (1)
    • dizilerde diziyi yeniden konumlandırmanız gerekir. O (n)
  • Bir öğeyi kaldırmak
    • Listelerde sadece işaretçileri yönlendirirsiniz. O (1).
    • dizilerde, kaldırılacak öğe dizinin ilk veya son öğesi değilse diziyi yeniden konumlandırmak için O (n) zaman harcarsınız; aksi takdirde işaretçiyi dizinin başlangıcına yeniden yerleştirebilir veya dizi uzunluğunu azaltabilirsiniz
  • Bir öğeyi bilinen bir konuma getirmek:
    • Listelerde, listeyi ilk öğeden belirli bir konumdaki öğeye götürmeniz gerekir. En kötü durum: O (n)
    • dizilerde öğeye hemen erişebilirsiniz. O (1)

Bu, bu iki popüler ve temel veri yapısının çok düşük seviyeli bir karşılaştırmasıdır ve listelerde kendi başına birçok değişiklik yapmanız gereken (öğeleri kaldırıp ekleyerek) listelerin daha iyi performans gösterdiğini görebilirsiniz. Öte yandan, dizinin öğelerine doğrudan erişmeniz gerektiğinde diziler listelerden daha iyi performans gösterir.

Bellek tahsisi açısından, listeler daha iyidir çünkü tüm öğelerin yan yana olmasına gerek yoktur. Öte yandan, işaretçileri bir sonraki (veya hatta bir önceki) öğeye depolamanın (küçük) ek yükü vardır.

Bu farklılıkları bilmek, geliştiricilerin uygulamalarında listeler ve diziler arasında seçim yapmaları açısından önemlidir.

Bunun listelerin ve dizilerin bir karşılaştırması olduğuna dikkat edin. Burada bildirilen sorunlara iyi çözümler var (örneğin: Atlama Listeleri, Dinamik Diziler, vb.). Bu cevapta her programcının bilmesi gereken temel veri yapısını dikkate aldım.


Bu, listelerin iyi bir şekilde uygulanması ve dizilerin kötü bir şekilde uygulanması için biraz doğrudur. Çoğu dizi uygulaması, onlara verdiğinizden çok daha karmaşıktır. Ve dinamik bellek ayırmanın ne kadar pahalı olabileceğini anladığınızı sanmıyorum.

Bu cevabın Veri Yapıları Üniversitesi ders programını kapsaması beklenmemektedir. Bu, sizin, benim ve çoğu insanın bildiği şekilde uygulanan Bağlı listeler ve diziler dikkate alınarak yazılmış bir karşılaştırmadır. Geometrik olarak genişleyen diziler, Listeleri Atla, vb ... bildiğim, kullandığım ve üzerinde çalıştığım çözümlerdir, ancak bu daha derin bir açıklamaya ihtiyaç duyar ve bu bir yığın akışı yanıtına uymaz.
Andrea Zilio

1
"Bellek tahsisi açısından, listeler daha iyidir çünkü tüm öğelerin yan yana olmasına gerek yoktur." Aksine, bitişik kaplar daha iyidir çünkü öğeleri yan yana tutarlar. Modern bilgisayarlarda, veri yerelliği kraldır. Bellekte zıplamak, önbellek performansınızı düşürür ve C ++ gibi dinamik bir dizi std::vectorile C ++ gibi bir bağlantılı listeden daha hızlı performans gösteren rastgele bir konuma (etkili) bir öğe ekleyen programlara yol açar std::list, çünkü basitçe liste çok pahalı.
David Stone

@DavidStone Belki yeterince net değildim, ancak bu cümleyle öğelerinizi depolamak için bitişik alana sahip olmanız gerekmediği gerçeğinden bahsediyordum. Özellikle, çok küçük olmayan bir şeyi depolamak istiyorsanız ve sınırlı kullanılabilir belleğiniz varsa, verilerinizi depolamak için yeterli bitişik boş alanınız olmayabilir , ancak bunun yerine muhtemelen verilerinizi bir liste kullanarak sığdırabilirsiniz (üst düzey işaretçileriniz olsa bile ... hem kapladıkları alan hem de bahsettiğiniz performans sorunları nedeniyle). Daha net hale getirmek için muhtemelen cevabımı güncellemeliyim.
Andrea Zilio

4

Tek bağlantılı liste, bir hücre ayırıcı veya nesne havuzundaki ücretsiz liste için iyi bir seçimdir:

  1. Yalnızca bir yığına ihtiyacınız var, bu nedenle tek bağlantılı bir liste yeterlidir.
  2. Her şey zaten düğümlere ayrılmıştır. Hücrelerin bir işaretçi içerecek kadar büyük olması koşuluyla, izinsiz bir liste düğümü için tahsisat ek yükü yoktur.
  3. Bir vektör veya deque, blok başına bir işaretçinin ek yükünü empoze eder. Yığını ilk oluşturduğunuzda, tüm hücrelerin ücretsiz olduğu göz önüne alındığında bu önemlidir, bu nedenle bu bir ön maliyettir. En kötü durumda, hücre başına bellek gereksinimini iki katına çıkarır.

Anlaştık. Ama aslında kaç programcı böyle şeyler yaratıyor? Çoğu, std :: list vs.'nin size verdiği şeyi basitçe yeniden uygulamaktadır. Ve aslında "müdahaleci" normalde sizin verdiğinizden biraz farklı bir anlama sahiptir - olası her liste öğesi, veriden ayrı bir işaretçi içerir.

1
Kaç? 0'dan fazla, bir milyondan az ;-) Jerry'nin sorusu "listeleri iyi kullanmak" mı, yoksa "her programcının günlük olarak kullandığı listeleri iyi kullanmak" mıydı yoksa arada bir şey miydi? Liste öğesi olan nesne içinde yer alan bir liste düğümü için "müdahaleci" dışında başka bir isim bilmiyorum - bir birleşmenin parçası olsun ya da olmasın. Nokta 3 yalnızca bunu yapmanıza izin veren dillerde geçerlidir - C, C ++, assembler good. Java kötü.
Steve Jessop

4

Çift bağlantılı liste, özellikle son erişime göre sipariş edildiğinde öğeler (Java'da LinkedHashMap) üzerinde bir sıra tanımlayan bir hashmap'in sırasını tanımlamak için iyi bir seçimdir:

  1. İlişkili bir vektörden veya sekmeden daha fazla bellek yükü (1 yerine 2 işaretçi), ancak daha iyi takma / çıkarma performansı.
  2. Zaten bir hash girişi için bir düğüme ihtiyacınız olduğu için tahsis ek yükü yoktur.
  3. Her bir nesneyi her iki şekilde belleğe çekmeniz gerekeceğinden, referansın yerelliği, bir vektör veya işaretçilerle karşılaştırıldığında ek bir sorun değildir.

Elbette, bir LRU önbelleğinin daha sofistike ve ayarlanabilir bir şeyle karşılaştırıldığında iyi bir fikir olup olmadığını tartışabilirsiniz, ancak eğer sahip olacaksanız, bu oldukça iyi bir uygulama. Bir vektör üzerinde ortadan silme ve sona ekleme işlemini her okuma erişiminde gerçekleştirmek istemezsiniz, ancak bir düğümü kuyruğa taşımak genellikle iyidir.


4

Bağlı listeler, verilerinizin nerede depolandığını kontrol edemediğinizde doğal seçimlerden biridir, ancak yine de bir nesneden diğerine geçmeniz gerekir.

Örneğin, C ++ 'da bellek izlemeyi uygularken (yeni / silme değiştirme), hangi işaretçilerin serbest bırakıldığını takip eden ve tam olarak kendiniz uygulamanız gereken bazı kontrol veri yapısına ihtiyacınız vardır. Bunun alternatifi, her veri parçasının başına bir bağlantılı liste eklemektir.

Silme denildiğinde listenin neresinde olduğunuzu her zaman hemen bildiğiniz için, O (1) 'de kolayca hafızadan vazgeçebilirsiniz. Ayrıca yeni malloced edilmiş yeni bir yığın eklemek O (1) 'de. Bu durumda listeyi yürümek çok nadiren gereklidir, bu nedenle burada O (n) maliyeti bir sorun değildir (bir yapıda yürümek zaten O (n) 'dur).


3

Yüksek hızlı itme, pop ve döndürmeye ihtiyaç duyduğunuzda ve O (n) indekslemeyi önemsemediğinizde kullanışlıdırlar.


Hiç C ++ bağlantılı listeleri bir deque ile karşılaştırarak (diyelim) zamanlamayı düşündünüz mü?

@Neil: Sahip olduğumu söyleyemem.
Ignacio Vazquez-Abrams

@Neil: Eğer C ++, diğer herhangi bir kapsayıcıdan daha yavaş hale getirmek için bağlantılı liste sınıfını kasıtlı olarak sabote ettiyse (ki bu gerçeklerden çok uzak değildir), bunun dilden bağımsız bir soruyla ne ilgisi var? Müdahaleci bir bağlantılı liste hala bağlantılı bir listedir.
Steve Jessop

@Steve C ++ bir dildir. Nasıl iradeye sahip olabileceğini göremiyorum. C ++ Komitesi üyelerinin bağlantılı listeleri bir şekilde sabote ettiğini öne sürüyorsanız (bu, birçok işlem için mantıksal olarak yavaş olmalıdır), o zaman suçlu adamları adlandırın!

3
Bu gerçekten sabotaj değildir - dış liste düğümlerinin avantajları vardır, ancak performans bunlardan biri değildir. Ancak, bildiğiniz aynı şeyin değiş tokuşunu yaparken kesinlikle herkes farkındaydı, bu da iyi bir kullanım bulmanın oldukça zor olduğudur std::list. Müdahaleci bir liste, konteyner öğeleriyle ilgili minimum gereksinimlerin C ++ felsefesine uymaz.
Steve Jessop

3

Tekil bağlantılı listeler, işlevsel programlama dillerinde ortak "liste" veri türünün açık uygulamasıdır:

  1. Kafasına ekleme hızlıdır ve (append (list x) (L))ve (append (list y) (L))neredeyse tüm verilerin paylaşabilirler. Yazma içermeyen bir dilde yazı üzerine kopyalamaya gerek yok. İşlevsel programcılar bundan nasıl yararlanacaklarını bilirler.
  2. Kuyruğa ekleme maalesef yavaştır, ancak başka herhangi bir uygulama da öyle olacaktır.

Karşılaştırıldığında, bir vektör veya deque, her iki uca da eklemek için genellikle yavaş olacaktır ve (en azından benim iki farklı ek örneğimde) tüm listenin (vektör) veya indeks bloğunun ve veri bloğunun bir kopyasının alınmasını gerektirir. ekli olmak (deque). Aslında, bazı nedenlerden dolayı kuyruğa eklenmesi gereken büyük listelerde deque için orada söylenecek bir şeyler olabilir, yargılamak için işlevsel programlama konusunda yeterince bilgim yok.


3

Bağlantılı bir liste için iyi bir kullanım örneği, liste elemanlarının çok büyük olduğu yerdir. Aynı anda yalnızca bir veya ikisinin CPU önbelleğine sığabileceği kadar büyük. Bu noktada, vektörler veya yinelemeye yönelik diziler gibi bitişik blok kapların sahip olduğu avantaj, az çok etkisiz hale getirilir ve gerçek zamanlı olarak birçok ekleme ve çıkarma meydana geliyorsa bir performans avantajı mümkün olabilir.


2

Deneyimlerime göre, seyrek matrisler ve fibonacci yığınları uygulamak. Bağlı listeler, bu tür veri yapılarının genel yapısı üzerinde size daha fazla kontrol sağlar. Seyrek matrislerin bağlantılı listeler kullanılarak en iyi şekilde uygulanıp uygulanmadığından emin olmasam da - muhtemelen daha iyi bir yol vardır, ancak lisans CS'de bağlantılı listeleri kullanarak seyrek matrislerin giriş ve çıkışlarını öğrenmeye gerçekten yardımcı oldu :)


1

Listelerde önemsiz olarak O (1) olan ve diğer veri yapılarında O (1) 'de uygulanması çok zor olan iki tamamlayıcı işlem vardır - öğelerin sırasını korumanız gerektiğini varsayarsak, keyfi bir konumdan bir öğeyi çıkarma ve ekleme.

Karma haritalar açıkça O (1) 'de ekleme ve silme yapabilir, ancak bu durumda öğeler üzerinde sırayla yineleme yapamazsınız.

Yukarıdaki gerçeği göz önünde bulundurarak, hash haritası, şık bir LRU önbelleği oluşturmak için bağlantılı bir listeyle birleştirilebilir: Sabit sayıda anahtar / değer çifti depolayan ve yenilerine yer açmak için en son erişilen anahtarı düşüren bir harita.

Karma haritadaki girişlerin bağlantılı liste düğümlerine işaretçiler içermesi gerekir. Karma haritaya erişirken, bağlantılı liste düğümü mevcut konumundan ayrılır ve listenin başına taşınır (O (1), bağlantılı listeler için yay!). En son kullanılan öğenin kaldırılması gerektiğinde, ilişkili karma harita girişiyle birlikte listenin kuyruğundaki öğenin bırakılması gerekir (yine O (1) işaretçiyi kuyruk düğümünde tuttuğunuzu varsayarsak) (yani geri bağlantılar karma haritanın listesi gereklidir.)


1

Bağlantılı bir listenin, tekrarla birbirine kenetlenen parçalar içeren bir sistemin Etki Alanı Odaklı Tasarım stili uygulamasında çok yararlı olabileceğini düşünün.

Akla gelen bir örnek, asılı bir zinciri modelleyecekseniz olabilir. Herhangi bir bağlantı üzerindeki gerilimin ne olduğunu bilmek istiyorsanız, arayüzünüz "görünür" ağırlık için bir alıcı içerebilir. Bunun uygulanması, bir sonraki bağlantısından görünen ağırlığını soran ve ardından sonuca kendi ağırlığını ekleyen bir bağlantı içerecektir. Bu şekilde, zincirin müşterisinden tek bir çağrı ile aşağıya kadar olan tüm uzunluk değerlendirilecektir.

Doğal dil gibi okuyan bir kod savunucusu olarak, bunun programcının zincir bağlantıya ne kadar ağırlık taşıdığını sormasına izin vermesini seviyorum. Ayrıca, zincir ağırlığı hesaplama hizmetine olan ihtiyacı ortadan kaldırarak, bağlantı uygulamasının sınırları içinde bu özelliklerin çocuklarını hesaplama endişesini de sürdürüyor. "


1

Ağ ve görüntü işleme, fizik motorları ve ışın izleme gibi performans açısından kritik alanlarda çalışan bağlantılı listeler için bulduğum en kullanışlı durumlardan biri, bağlantılı listeleri kullanmanın aslında referansın yerini iyileştirmesi ve yığın tahsislerini azaltması ve hatta bazen bellek kullanımını azaltmasıdır. basit alternatifler.

Şimdi bu, bağlantılı listelerin, genellikle tam tersini yaptıkları için tüm bunları yapabilecekleri tam bir tezat gibi görünebilir, ancak her liste düğümünün, izin vermek için yararlanabileceğimiz sabit bir boyut ve hizalama gereksinimleri olması bakımından benzersiz bir özelliği vardır. bunların bitişik olarak depolanması ve değişken boyutlu şeylerin yapamayacağı şekilde sabit zamanda kaldırılması.

Sonuç olarak, bir milyon iç içe geçmiş değişken uzunluklu alt dizi içeren değişken uzunluklu bir diziyi depolamanın analojik eşdeğerini yapmak istediğimiz bir durumu ele alalım. Somut bir örnek, bir milyon çokgeni (bazı üçgenler, bazı dörtgenler, bazı beşgenler, bazı altıgenler vb.) Depolayan dizinlenmiş bir ağdır ve bazen çokgenler ağın herhangi bir yerinden kaldırılır ve bazen çokgenler, mevcut bir çokgene bir tepe noktası eklemek için yeniden oluşturulur veya birini kaldır. Bu durumda, bir milyon minik depolarsak std::vectors, her bir vektör için bir yığın tahsisi ve potansiyel olarak patlayıcı bellek kullanımı ile karşı karşıya kalırız. Bir milyon minik SmallVectorsbu sorunu yaygın durumlarda o kadar fazla yaşamayabilir, ancak daha sonra ayrı ayrı yığın ayrılmamış önceden tahsis edilmiş arabellekleri yine de patlayıcı bellek kullanımına neden olabilir.

Buradaki sorun, bir milyon std::vectorörneğin bir milyon değişken uzunluklu şeyi depolamaya çalışıyor olmasıdır. Değişken uzunluklu şeyler, içeriklerini öbek üzerinde başka bir yerde depolamamışlarsa, bitişik olarak çok etkili bir şekilde depolanamayacakları ve sabit zamanda (en azından çok karmaşık bir ayırıcı olmadan basit bir şekilde) kaldırılamayacakları için bir yığın tahsisi isteme eğilimindedir.

Bunun yerine şunu yaparsak:

struct FaceVertex
{
    // Points to next vertex in polygon or -1
    // if we're at the end of the polygon.
    int next;
    ...
};

struct Polygon
{
     // Points to first vertex in polygon.
    int first_vertex;
    ...
};

struct Mesh
{
    // Stores all the face vertices for all polygons.
    std::vector<FaceVertex> fvs;

    // Stores all the polygons.
    std::vector<Polygon> polys;
};

... daha sonra yığın ayırma ve önbellek kaçırma sayısını önemli ölçüde azalttık. Bir yığın tahsisi gerektirmek ve eriştiğimiz her bir çokgen için potansiyel olarak zorunlu önbellek kaçırmak yerine, artık yalnızca tüm ağda depolanan iki vektörden biri kapasitelerini aştığında (amorti edilmiş bir maliyet) bu yığın tahsisine ihtiyaç duyuyoruz. Ve bir tepe noktasından diğerine geçme adımları yine de önbellek payının ıskalamasına neden olsa da, düğümler bitişik olarak depolandığından ve komşu bir tepe noktasının olabileceği bir olasılık olduğundan, her bir çokgenin ayrı bir dinamik dizi depolamasından daha azdır. çıkarılmadan önce erişilebilir olmalıdır (özellikle birçok çokgenin köşelerini aynı anda ekleyeceği ve bu da aslanın çokgen köşelerindeki payını mükemmel şekilde bitişik hale getireceği düşünülürse).

İşte başka bir örnek:

görüntü açıklamasını buraya girin

... ızgara hücrelerinin, her bir karede hareket eden 16 milyon parçacık için parçacık-parçacık çarpışmasını hızlandırmak için kullanıldığı yerlerde. Bu parçacık ızgarası örneğinde, bağlantılı listeleri kullanarak, bir parçacığı bir ızgara hücresinden diğerine yalnızca 3 endeksi değiştirerek taşıyabiliriz. Bir vektörden silmek ve diğerine geri itmek, çok daha pahalı olabilir ve daha fazla yığın tahsisatı sağlayabilir. Bağlantılı listeler ayrıca bir hücrenin belleğini 32 bit'e düşürür. Bir vektör, uygulamaya bağlı olarak, dinamik dizisini boş bir vektör için 32 bayt alabileceği noktaya önceden tahsis edebilir. Yaklaşık bir milyon ızgara hücremiz varsa, bu oldukça büyük bir farktır.

... ve bu günlerde bağlantılı listeleri en kullanışlı bulduğum yer burası ve özellikle "indekslenmiş bağlantılı liste" çeşidini faydalı buluyorum çünkü 32 bit indeksler, 64 bit makinelerdeki bağlantıların bellek gereksinimlerini yarıya indiriyor ve düğümler bir dizide bitişik olarak depolanır.

Sık sık bunları, her yerde sabit zamanlı kaldırmalara ve eklemelere izin vermek için dizine alınmış ücretsiz listelerle birleştiriyorum:

görüntü açıklamasını buraya girin

Bu durumda, nextdizin ya düğüm kaldırılmışsa bir sonraki boş dizini ya da düğüm kaldırılmamışsa bir sonraki kullanılan dizini gösterir.

Ve bu, bugünlerde bağlantılı listeler için bulduğum bir numaralı kullanım örneği. Örneğin, her biri 4 öğenin ortalamasını alan bir milyon değişken uzunlukta alt diziyi depolamak istediğimizde (ancak bazen öğeler kaldırılır ve bu alt dizilerden birine eklenir), bağlantılı liste 4 milyon her biri ayrı ayrı yığın olarak ayrılmış olan 1 milyon konteyner yerine bitişik olarak bağlantılı liste düğümleri: bir dev vektör, yani bir milyon küçük değil.


0

Geçmişte bir C / C ++ uygulamasında bağlantılı listeler (hatta çift bağlantılı listeler) kullandım. Bu .NET ve hatta stl'den önceydi.

Muhtemelen şimdi bir .NET dilinde bağlantılı bir liste kullanmazdım çünkü ihtiyacınız olan tüm geçiş kodu, Linq uzantı yöntemleri aracılığıyla sizin için sağlanır.

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.