Bağlantılı Listelerde her zaman kuyruk işaretçisi olmalı mı?


11

Anlayışım ...

Avantajları:

  • Sona ekleme O (N) yerine O (1) şeklindedir.
  • Liste İki Katına Bağlı Bir Listeyse, sondan çıkarılması O (N) yerine O (1) olur.

dezavantajı:

  • Önemsiz miktarda fazladan bellek alır: 4-8 bayt .
  • Uygulayıcı kuyruğu takip etmek zorundadır.

Bu avantajlara ve dezavantajlara baktığımda, Bağlantılı Listenin neden bir kuyruk işaretçisi kullanmaktan kaçındığını göremiyorum. Kaçırdığım bir şey var mı?


1
kuyruk işaretçisi 4-8 bayttır (32 veya 64 bit sisteme bağlı olarak)
cırcır ucube

1
Çoktan özetlediniz.
Robert Harvey

@RobertHarvey Şu anda veri yapılarını inceliyorum ve en iyi uygulamaların ne olduğunun farkında değilim. Yazdığım şey benim izlenimlerim, ama sorduğum şey doğruysa. Ama açıkladığınız için teşekkürler!
Adam Zerner

7
"En iyi uygulamalar" kitlelerin afyonudur . Hala kendiniz için düşünme yeteneğinizin olduğunu kutlayın.
Robert Harvey

@RobertHarvey bağlantısı için teşekkürler - Bu noktayı seviyorum! Kesinlikle durumun ayrıntılarına bakan bir fayda-maliyet yaklaşımı benimsiyorum.
Adam Zerner

Yanıtlar:


7

Haklısın, bir kuyruk işaretçisi asla acıtmaz ve sadece yardımcı olabilir. Bununla birlikte, hiç bir kuyruk işaretçisine ihtiyaç duymadığı bir durum vardır.

Bir yığının uygulanması için bağlantılı bir liste kullanılıyorsa, bir kuyruk işaretçisine gerek yoktur, çünkü kişi tüm erişim, ekleme ve kaldırma işlemlerinin kafada gerçekleştiğini garanti edebilir. Bu varlık bir kütüphane ya platformda standart uygulamasıdır ve hafıza ucuz, ama bir değil çünkü biri zaten bir kuyruk işaretçisi ile doubly bağlı listesini kullanın olabileceğini söyledi ihtiyacım bunu.


9

Bağlantılı listeler genellikle kalıcı ve değişmezdir. Aslında, fonksiyonel programlama dillerinde, bu kullanım her yerde bulunur. Kuyruk göstergeleri bu özelliklerin her ikisini de kırmaktadır. Bununla birlikte, değişmezliği veya kalıcılığı umursamıyorsanız, bir kuyruk işaretçisi eklemenin çok az bir dezavantajı vardır.


3
Neden kalıcılığı ve değişmezliği kırdıklarını açıklar mısınız?
Adam Zerner

Lütfen önbellek dostu endişe ekleyin
Basilevs

Bu sorudaki örneğime bak . Sadece listenin başından çalışıyorsanız ve değişmezse, kuyruğu paylaşabilirsiniz. Bir kuyruk işaretçisi kullanıyorsanız, bu tekniği değişmezliği paylaşmak ve korumak için kullanamazsınız.
Karl Bielefeldt

Aslında değişmezlikle birlikte bir kuyruk işaretçisi işe yaramaz çünkü onunla yapabileceğiniz tek şey son elementin ne olduğunu görmektir. Diğer her şeyin baştan çalışması gerekiyor.
cırcır ucube

0

Bağlantılı listeler için nadiren bir kuyruk işaretçisi kullanıyorum ve yığın ve benzer bir ekleme / çıkarma deseninin (veya yalnızca ortadan doğrusal zamanla çıkarmanın) yeterli olduğu yerlerde, tekli bağlantılı listeleri daha sık kullanma eğilimindeyim. Çünkü yaygın kullanım durumlarımda, kuyruk işaretçisi listesinin çift bağlantılı bir listeye dönüştürülmesi pahalı olduğu için aslında pahalı.

Çoğunlukla tekil bağlantılı bir liste için genel vaka kullanımım, her biri yalnızca birkaç liste düğümü içeren yüz binlerce bağlantılı liste saklayabilir. Ayrıca genellikle bağlantılı listeler için işaretçiler kullanmıyorum. Endeksleri 32 bit olabileceği için bir dizinin içine indeksler kullanıyorum, örneğin 64 bitlik bir işaretçinin yarısını alan. Ayrıca genellikle liste düğümlerini birer birer ayırmıyorum ve bunun yerine, yine, tüm düğümleri saklamak için büyük bir dizi kullanın ve daha sonra düğümleri birbirine bağlamak için 32 bit indeksleri kullanın.

Örnek olarak, çarpışma algılamayı hızlandırmak için hareket eden ve birbirinden seken bir milyon parçacığı bölmek için 400x400 ızgara kullanan bir video oyunu düşünün. Bu durumda, depolamanın oldukça verimli bir yolu, 160.000 tek bağlantılı listeyi depolamaktır, bu da benim durumumda 160.000 32 bit tam sayıya (~ 640 kilobayt) ve parçacık başına bir 32 bit tam sayı ek yüküne dönüşür. Parçacıklar ekranda hareket ettikçe, tek yapmamız gereken bir parçacığı bir hücreden diğerine taşımak için birkaç 32 bit tamsayıyı güncellemektir:

resim açıklamasını buraya girin

... nextya bir parçacık düğümünün indeksi ("işaretçisi"), ya hücredeki bir sonraki parçacığa ya da parçacığın öldüğü takdirde geri kazanılacak bir sonraki serbest parçacığa bir indeks görevi görür (temel olarak indeksleri kullanarak bir serbest liste ayırıcı uygulaması):

resim açıklamasını buraya girin

Hücreden doğrusal zamanın çıkarılması aslında bir ek yük değildir, çünkü parçacık mantığını bir hücredeki parçacıklar arasında yineleyerek işliyoruz, bu nedenle çift bağlantılı bir liste sadece yararlı olmayan bir tür ek yükü ekleyecektir. benim durumumda bir kuyruk gibi bana da faydası olmazdı.

Bir kuyruk işaretçisi, ızgaranın bellek kullanımını iki katına çıkaracak ve önbellek kaçırma sayısını artıracaktır. Ayrıca, dalsız olmak yerine listenin boş olup olmadığını denetlemek için bir dal gerektirmesi için ekleme yapılması gerekir. Çift bağlantılı bir liste yapmak, her parçacığın liste ek yükünü iki katına çıkarır. Bağlantılı listeleri kullandığım zamanın% 90'ı, bu gibi durumlar için ve bu yüzden bir kuyruk işaretçisinin depolanması nispeten pahalı olurdu.

Yani 4-8 bayt, aslında bağlantılı listeleri ilk başta kullandığım bağlamların çoğunda önemsiz değil. Sadece bir çip yükü depolamak için bir veri yapısı kullanıyorsanız, o zaman 4-8 bayt her zaman önemsiz olmayabilir. Ben aslında patlayıcı bellek kullanımı (genellikle bir işaretçi artı en az ızgara hücre başına iki tamsayı) olacak ızgara için büyüyen 160.000 dinamik diziler depolamak yerine gerekli bellek bellek sayısını ve bellek miktarını azaltmak için bağlantılı listeleri kullanın hücre başına tek başına bir tamsayı ve hücre başına sıfır yığın tahsisi yerine ızgara hücresi başına yığın atamaları).

LL'lerin genel yakınlık eksikliğinden dolayı bu durumlarda genellikle kötü bir seçim olduğu durumlarda, çoğu zaman ön / orta kaldırma ve ön / orta yerleştirme ile ilişkili sabit zamanlı karmaşıklıkları için bağlantılı listelere ulaşan birçok insan bulurum. LL'lerin performans açısından bana göre güzel olduğu yerlerde, sadece birkaç noktayı manipüle ederek ve değişken boyutlu bir bellek ayırıcısı olmadan değişken boyutlu bir veri yapısına ulaşarak bir öğeyi bir listeden diğerine taşıma yeteneği (çünkü her bir düğümün tek biçimli bir boyutu vardır, ücretsiz listeleri kullanabiliriz, örneğin). Her liste düğümü genel amaçlı bir ayırıcıya karşı ayrı ayrı tahsis ediliyorsa, bu genellikle bağlantılı listelerin alternatiflere göre çok daha kötü olduğunu ve

Bunun yerine, bağlantılı listelerin basit alternatiflere göre çok etkili bir optimizasyon görevi gördüğü çoğu durumda, en kullanışlı formlar genellikle tekli bağlantılıdır, sadece bir baş işaretçisine ihtiyaç duyar ve başına genel amaçlı bellek tahsisi gerektirmez Bunun yerine genellikle düğüm başına zaten tahsis edilmiş belleği bir araya getirebilir (önceden tahsis edilmiş büyük bir diziden, örneğin). Ayrıca her SLL, genellikle bir grafik düğümüne bağlı kenarlar gibi çok az sayıda öğeyi depolar (tek bir büyük bağlantılı listenin aksine birçok küçük bağlantılı liste).

Bu günlerde bir DRAM tekne yükümüz olduğunu da unutmamak gerekir, ancak bu, ikinci en yavaş bellek türüdür. 64 bayt önbellek satırlarına sahip L1 önbellek söz konusu olduğunda hala çekirdek başına 64 KB gibi bir şeydeyiz. Sonuç olarak, bu küçük bayt tasarrufu, milyonlarca kez bir önbellek hattına iki kat depolamak veya aramamak arasındaki fark anlamına gelirse, yukarıdaki parçacık sim gibi performans açısından kritik bir alanda gerçekten önemli olabilir.

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.