Bu ikisi birbirine çok benziyor ve neredeyse aynı bir yapıya sahipler. Fark ne? Her birinin farklı işlemleri için zaman karmaşıklıkları nelerdir?
Bu ikisi birbirine çok benziyor ve neredeyse aynı bir yapıya sahipler. Fark ne? Her birinin farklı işlemleri için zaman karmaşıklıkları nelerdir?
Yanıtlar:
Yığın, sadece yüksek seviyelerdeki elemanların düşük seviyelerdeki elemanlardan daha büyük (azami yığın için) veya daha küçük (en az yığın için) olduğunu garanti ederken, BST siparişi ("sol" dan "sağa") garanti eder. Sıralanmış elemanları istiyorsanız, BST ile gidin. Dante tarafından bir inek değil
Öbek findMin / findMax'ta (O (1)) daha iyidir, BST ise tüm buluntularda iyidir (O (logN)). Her iki yapı için de Insert (O) logL'dir. Yalnızca findMin / findMax (örneğin öncelik ile ilgili) ile ilgileniyorsanız, öbek ile gidin. Her şeyin sıralanmasını istiyorsanız, BST ile gidin.
Hem ikili arama ağaçları hem de ikili yığınlar ağaç tabanlı veri yapılarıdır.
Yığınlar, düğümlerin çocukları üzerinde bir önceliğe sahip olmasını gerektirir. Maksimum bir yığında, her düğümün çocukları kendisinden daha az olmalıdır. Bir dk öbek için tam tersi:
İkili arama ağaçları (BST) kardeş düğümleri arasında belirli bir sıralamayı (ön sipariş, sipariş sırası, sipariş sonrası) izler. Ağaç gerekir yığınları aksine sıralanabilir:
BST, ekleme, silme ve arama için ortalama değerine sahiptir .
İkili Yığınlar findMin / findMax için ortalama ve ekleme ve silme için değerine sahiptir .O ( 1 ) O ( log n )
özet
Type BST (*) Heap
Insert average log(n) 1
Insert worst log(n) log(n) or n (***)
Find any worst log(n) n
Find max worst 1 (**) 1
Create worst n log(n) n
Delete worst log(n) log(n)
Bu tablodaki tüm ortalama zamanlar Insert dışındaki en kötü zamanlarla aynıdır.
*
: bu cevabın her yerinde BST == Dengeli BST, çünkü dengesiz asimptotik olarak berbat**
: bu cevapta açıklanan önemsiz bir değişiklik kullanarak***
: log(n)
işaretçi ağacı yığını n
için, dinamik dizi yığını içinBST üzerinde ikili yığının avantajları
O(1)
BST için bir ikili öbek içine ortalama zaman ekleme O(log(n))
. Bu yığınların katil özelliğidir.
Asibtotik olmayan performans nedeniyle pratik olmasalar da O(1)
, Fibonacci Heap gibi amortize edilmiş (daha güçlü) ve Brodal kuyruğu gibi en kötü durumda olan diğer yığınlar da vardır : https://stackoverflow.com/questions/30782636 / Var-Fibonacci-yığınları-ya-Brodal-kuyruklar kullanılan in uygulamaya yerde
İkili yığınlar, dinamik dizilerin veya işaretçi tabanlı ağaçların, yalnızca işaretçi tabanlı ağaçların üzerine etkin bir şekilde uygulanabilir . Öyleyse, zaman zaman yeniden boyutlandırma gecikmelerini karşılayabilirsek, öbek için daha fazla yer etkin olan dizi uygulamasını seçebiliriz.
ikili yığın oluşturma olduğu O(n)
kötü durum , O(n log(n))
BST için.
BST'nin ikili yığın üzerinden avantajı
keyfi öğeleri arayın O(log(n))
. Bu BST'lerin katil özelliğidir.
Yığın için, O(n)
genel olarak, en büyük olan hariç O(1)
.
BST üzerindeki yığının "Yanlış" avantajı
Öbek O(1)
en fazla BST O(log(n))
.
Bu yaygın bir yanılgıdır, çünkü en büyük öğenin izini sürmek için bir BST'yi değiştirmek ve bu öğenin ne zaman değiştirilebileceğini güncellemek çok önemlidir: daha büyük bir takasın yerleştirilmesinde, kaldırırken en büyük ikinciyi bulabilirsiniz. https://stackoverflow.com/questions/7878622/can-we-use-binary-search-tree-to-simulate-heap-operation ( Yeo tarafından belirtilmiştir ).
Aslında, bu BST'lere kıyasla yığınların bir kısıtlamasıdır : tek etkili arama, en büyük eleman için olandır .
Ortalama ikili yığın ekleme O(1)
Kaynaklar:
Sezgisel argüman:
İkili bir yığında, belirli bir endekste değeri arttırmak da O(1)
aynı sebepten dolayıdır. Ancak bunu yapmak istiyorsanız, yığın işlemleri hakkında ek bir endeks güncel tutmak isteyebilirsiniz, https://stackoverflow.com/questions/17009056/how-to-implement-ologn-decrease- Min-yığın-temelli-öncelik-kuyruğu için anahtar-işlem örneğin Dijkstra için. Ekstra ücret ödemeden mümkün.
GCC C ++ standart kütüphane gerçek donanımda bir referans niteliği taşıyor
Ekleme zamanları hakkında haklı olup olmadığımı görmek için C ++ std::set
( Kırmızı-siyah ağaç BST ) ve std::priority_queue
( dinamik dizi yığını ) ekini karşılaştırdım ve elde ettiğim şey bu:
Çok açıkça:
Öbek ekleme zamanı temelde sabittir.
Dinamik dizi yeniden boyutlandırma noktalarını açıkça görebiliyoruz. Sistem gürültüsünün üstündeki her şeyi görebilmemiz için her 10k parçanın ortalamasını aldığımız için , bu tepeler aslında gösterilenden yaklaşık 10k kat daha büyük!
Yakınlaştırılmış grafik temelde yalnızca dizi yeniden boyutlandırma noktalarını hariç tutar ve hemen hemen tüm eklerin 25 nanosaniyenin altına düştüğünü gösterir.
BST logaritmiktir. Tüm uçlar, ortalama yığın ekinden çok daha yavaştır.
BST vs hashmap ayrıntılı analiz: https://stackoverflow.com/questions/18414579/what-data-structure-is-inside-stdmap-in-c/51945119#51945119
GCC C + + standart kütüphane
gem5 tam bir sistem simülatörüdür ve bu nedenle ile birlikte sonsuz hassas bir saat sağlar m5 dumpstats
. Bu yüzden bireysel ekler için zamanlamaları tahmin etmek için kullanmaya çalıştım.
Yorumlama:
yığın hala sabittir, fakat şimdi daha ayrıntılı olarak birkaç satır olduğunu görüyoruz ve daha yüksek olan her satır daha seyrek.
Bu, bellek erişim gecikmelerine karşılık gelmelidir yüksek ve yüksek kesici uçlar için yapılır.
TODO BST'yi tam olarak yorumlayamıyorum, çünkü logaritmik ve biraz daha sabit görünmüyor.
Ancak bu daha büyük ayrıntıyla görebildiğimiz birkaç farklı çizgiyi görebiliyoruz, ancak bunların neyi temsil ettiğinden emin değilim: En alt çizgiyi yerleştirdiğimiz için en alt çizginin daha ince olmasını bekliyorum.
Aarch64 HPI CPU'da bu Buildroot kurulumu ile karşılaştırılmıştır .
BST bir dizi üzerinde verimli bir şekilde uygulanamaz
Yığın işlemlerinin yalnızca tek bir ağaç dalına kabarması veya aşağı kabarması gerekir, bu nedenle O(log(n))
en kötü durum değişimleri O(1)
ortalamadır.
Bir BST'yi dengede tutmak, üst öğeyi bir başkası için değiştirebilen ve tüm dizinin etrafında hareket etmesini gerektiren ağaç dönüşleri gerektirir ( O(n)
).
Yığınlar bir dizi üzerinde verimli şekilde uygulanabilir
Ebeveyn ve çocuk dizinleri, burada gösterildiği gibi mevcut dizinden hesaplanabilir .
BST gibi dengeleme işlemleri yoktur.
Min Sil, yukarıdan aşağıya doğru olması gereken en endişe verici işlemdir. Ancak her zaman burada açıklandığı gibi yığının tek bir dalını "aşağı çevirerek" yapılabilir . Yığın her zaman iyi dengelendiğinden, bu O (log (n)) en kötü duruma yol açar.
Kaldırdığınız her biri için tek bir düğüm ekliyorsanız, yığınların silme baskınlığı sağladığı gibi sağladığı asimptotik O (1) ortalama eklemenin avantajını kaybedersiniz ve bir BST kullanabilirsiniz. Dijkstra her düğüm için birkaç kez düğüm günceller, bu yüzden biz iyiyiz.
Dinamik dizi kümeleri vs işaretçi ağacı yığınları
Yığınlar işaretçi yığınlarının üstüne etkili bir şekilde uygulanabilir: https://stackoverflow.com/questions/19720438/is-it-possible-to-make-efficient-pointer-based-binary-heap-implementations
Dinamik dizi uygulaması daha fazla alan verimlidir. Her bir yığın öğesinin sadece bir gösterici içerdiğini varsayalım struct
:
ağaç uygulaması her eleman için üç işaretçi saklamalıdır: ebeveyn, sol çocuk ve sağ çocuk. Yani hafıza kullanımı her zaman 4n
(3 ağaç işaretçisi + 1 struct
işaretçi).
Ağaç BST'leri ayrıca daha fazla dengeleme bilgisine ihtiyaç duyacaktır, örneğin siyah-kırmızılık.
dinamik dizi uygulamasının 2n
iki katına çıktıktan hemen sonra büyüklüğü olabilir . Yani ortalama olarak olacak 1.5n
.
Öte yandan, ağaç yığınının en kötü durumda olması daha iyidir, çünkü destek dinamik dizisinin boyutunu iki katına kopyalamak en O(n)
kötü durumu alırken, ağaç yığını her düğüm için yeni küçük tahsisler yapar.
Yine de, destek dizisinin iki katına çıkarılması O(1)
itfa edilir, bu nedenle azami gecikme süresine varır. Burada belirtilen .
Felsefe
BST'ler, bir ebeveyn ile tüm soyundan gelenler arasında küresel bir özellik sürdürür (daha küçük, sağ daha büyük).
Bir BST'nin üst düğümü, sürdürülmesi için küresel bilgi gerektiren orta elementtir (kaç tane daha küçük ve daha büyük element olduğunu bilmek).
Bu global mülkün bakımı daha pahalıdır (log n insert), ancak daha güçlü aramalar sağlar (log n search).
Yığınlar, ebeveyn ile doğrudan çocuk arasında yerel bir özellik sağlar (ebeveyn> çocuklar).
Bir yığının en üst notu, yalnızca korumak için yerel bilgi gerektiren (ebeveyni bilmek) büyük unsurdur.
Çift bağlantılı liste
İkili bağlantılı bir liste, ilk öğenin en büyük önceliğe sahip olduğu yığının alt kümesi olarak görülebilir, bu yüzden bunları burada da karşılaştıralım:
O(1)
Maddelere dair işaretçilerimiz bulunduğundan en kötü durum ve güncelleme gerçekten basittir.O(1)
ortalama, bağlantılı listeden daha kötü. Daha genel yerleştirme pozisyonuna sahip olduğu için tradeoff.O(n)
her ikisi için deBunun için bir kullanım durumu, öbek anahtarının geçerli zaman damgası olduğu durumdur: bu durumda, yeni girişler her zaman listenin başına gider. Böylece tam zaman damgasını tamamen unutabiliriz ve listedeki sırasını öncelik olarak tutabiliriz.
Bu, bir LRU önbelleğini uygulamak için kullanılabilir . Dijkstra gibi yığın uygulamaları için olduğu gibi , hangi düğümün hızlı bir şekilde güncelleneceğini bulmak için, anahtardan listenin ilgili düğümüne ek bir hashmap tutmak isteyeceksiniz.
Farklı Dengeli BST'nin karşılaştırılması
Asimptotik, şu ana kadar gördüğüm "Dengeli BST" olarak sınıflandırılan tüm veri yapıları için zaman ekleyip bulsa da aynı olsa da, farklı BBST'lerin farklı değişimleri var. Bunu henüz tam olarak çalışmadım, ancak bu takasları burada özetlemek iyi olur:
Ayrıca bakınız
CS'de benzer soru: İkili arama ağacı ile ikili yığın arasındaki fark nedir?
Veri yapısı ile ilgili endişe düzeylerini ayırt etmek zorundasınız.
Soyut veri yapıları , bu, söz konusu (saklı nesneler, operasyon) farklıdır. Biri öncelik sırasını, diğeri bir kümeyi uygular. Bir öncelik sırası, yalnızca en büyük önceliğe sahip olan rasgele bir öğe bulmakla ilgilenmez.
Somut uygulama yapılarının. Burada ilk bakışta her ikisi de (ikili) ağaçlardır, ancak farklı yapısal özelliklere sahiptir. Hem anahtarların göreceli sıralaması hem de olası küresel yapılar farklıdır. (Bir miktar BST
anahtar içinde soldan sağa doğru sıralanır, bir yığın halinde yukarıdan aşağıya sıralanırlar.) IPlant'ın doğru bir şekilde söylediği gibi bir yığın da "tamamlanmış" olmalıdır.
Düşük seviye uygulamasında son bir fark var . Dengesiz bir ikili arama ağacında, işaretçiler kullanılarak yapılan standart bir uygulama vardır. Aksine bir ikili yığın, bir diziyi kullanarak (tam olarak kısıtlanmış yapı nedeniyle) etkin bir uygulamaya sahiptir.