Bu konuda gömülü birkaç soru olduğunu düşünüyorum:
- O (n) zamanında
buildHeap
çalışması için nasıl uygularsınız ?
- Doğru uygulandığında O (n) zamanında
buildHeap
çalıştığını nasıl gösterirsiniz ?
- Neden aynı mantık öbek sıralamasının O (n log n) yerine O (n) zamanında çalışmasını sağlamak için çalışmıyor ?
O (n) zamanında buildHeap
çalışması için nasıl uygularsınız ?
Genellikle bu soruların cevapları arasındaki fark odaklanmak siftUp
ve siftDown
. O (n) performansını elde etmek için siftUp
ve arasında doğru seçim yapmak siftDown
önemlidir , ancak genel ve arasındaki farkı anlamasına yardımcı olacak hiçbir şey yapmaz . Gerçekten de, hem uygun uygulamaları ve olacak sadece kullanmak . Örneğin, bir ikili yığın kullanarak bir öncelik sırası uygulamak için kullanılacak böylece operasyon sadece varolan yığın haline ekler gerçekleştirmek için gereklidir.buildHeap
buildHeap
heapSort
buildHeap
heapSort
siftDown
siftUp
Maks yığının nasıl çalıştığını açıklamak için bunu yazdım. Bu, genellikle yığın sıralaması için veya daha yüksek değerlerin daha yüksek önceliği işaret ettiği bir öncelik kuyruğu için kullanılan yığın türüdür. Min yığını da yararlıdır; örneğin, tamsayı tuşlarına sahip öğeleri artan sırada veya dizeleri alfabetik sırada alırken. İlkeler tamamen aynıdır; sadece sıralama düzenini değiştirin.
Yığın özellik belirtir bir ikili yığın her düğüm en azından alt öğelerinden ikisi kadar büyük olması gerektiğini. Özellikle, bu öbekteki en büyük öğenin kökte olduğu anlamına gelir. Aşağı eleme ve eleme, ters yönde aynı işlemdir: rahatsız edici bir düğümü, heap özelliğini karşılayana kadar taşıyın:
siftDown
en azından altındaki her iki düğüm kadar büyük olana kadar en büyük çocuğuyla (böylece aşağı doğru hareket ettirerek) çok küçük bir düğümü değiştirir.
siftUp
üstündeki düğümden daha büyük olmayana kadar üst öğesiyle çok büyük bir düğümü (böylece yukarı doğru hareket ettirerek) değiştirir.
İşlemlerinin sayısı için gerekli olan siftDown
ve siftUp
düğüm taşımak gerekebilir mesafesi ile orantılıdır. Çünkü siftDown
, ağacın tabanına olan mesafedir, bu nedenle ağacın siftDown
üstündeki düğümler için pahalıdır. İle siftUp
, iş ağacın tepesine olan mesafe ile orantılıdır, bu nedenle ağacın siftUp
altındaki düğümler için pahalıdır. Her iki işlem de en kötü durumda O (log n) olmasına rağmen , bir yığın içinde üstte sadece bir düğüm bulunurken, düğümlerin yarısı alt katmanda yer alır. Yani biz her düğüme bir operasyon uygulamak zorunda, biz tercih edeceğini çok şaşırtıcı olmamalıdır siftDown
üzerinde siftUp
.
buildHeap
Hepsi bu suretle geçerli bir yığın üreten yığın özelliğini tatmin dek fonksiyon sıralanmamış öğeler ve hamle onları bir dizi alır. Birinin tanımladığımız ve işlemlerini buildHeap
kullanmak için alabileceği iki yaklaşım vardır .siftUp
siftDown
Yığının üstünden (dizinin başlangıcından) başlayın ve siftUp
her öğeyi arayın . Her adımda, önceden elenmiş öğeler (dizideki geçerli öğeden önceki öğeler) geçerli bir yığın oluşturur ve sonraki öğenin yukarı kaydırılması, öğeyi yığında geçerli bir konuma yerleştirir. Her düğümü eledikten sonra, tüm öğeler heap özelliğini karşılar.
Ya da, ters yönde gidin: dizinin sonundan başlayın ve öne doğru geri gidin. Her yinelemede, bir öğeyi doğru konuma gelene kadar eleyin.
Hangi uygulama buildHeap
daha verimli?
Bu çözümlerin her ikisi de geçerli bir yığın oluşturacaktır. Şaşırtıcı olmayan bir şekilde, daha verimli olan ikinci işlemdir siftDown
.
H = log n , yığının yüksekliğini temsil etsin . siftDown
Yaklaşım için gerekli olan çalışma toplamla verilir.
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
Toplamdaki her terim, belirli bir yükseklikteki bir düğümün hareket etmesi gereken maksimum mesafeye sahiptir (alt katman için sıfır, kök için h), bu yükseklikteki düğüm sayısıyla çarpılır. Buna karşılık, siftUp
her bir düğümü çağırmak için toplam
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
İkinci toplamın daha büyük olduğu açık olmalıdır. Sadece birinci terim hn / 2 = 1/2 n log n'dir , bu nedenle bu yaklaşım en iyi O (n log n) ' de karmaşıklığa sahiptir .
siftDown
Gerçekten O (n) yaklaşımının toplamını nasıl kanıtlarız ?
Bir yöntem (aynı zamanda işe yarayan başka analizler de vardır) sonlu toplamı sonsuz bir seriye dönüştürmek ve daha sonra Taylor serisini kullanmaktır. Sıfır olan ilk terimi göz ardı edebiliriz:
Bu adımların her birinin neden işe yaradığından emin değilseniz, buradaki kelimeler için bir gerekçe:
- Terimlerin hepsi pozitiftir, bu nedenle sonlu toplam sonsuzdan küçük olmalıdır.
- Seri, x = 1/2 olarak değerlendirilen bir güç serisine eşittir .
- Bu kuvvet serisi, f (x) = 1 / (1-x) için Taylor serisinin türevine (sabit bir kez) eşittir .
- x = 1/2 bu Taylor serisinin yakınsama aralığındadır.
- Bu nedenle, Taylor serisini 1 / (1-x) ile değiştirebilir , farklılaştırabilir ve sonsuz serilerin değerini bulmak için değerlendirebiliriz.
Sonsuz toplam tam olarak n olduğundan, sonlu toplamın daha büyük olmadığı ve bu nedenle O (n) olduğu sonucuna vardık .
Öbek sıralaması neden O (n log n) süresi gerektirir?
buildHeap
Doğrusal zamanda çalışmak mümkünse , yığın sıralaması neden O (n log n) süresi gerektirir? Öbek türü iki aşamadan oluşur. İlk olarak, en iyi şekilde uygulandığında O (n) zamanı buildHeap
gerektiren diziyi çağırırız . Bir sonraki aşama, yığındaki en büyük öğeyi tekrar tekrar silmek ve dizinin sonuna koymaktır. Bir öğeyi yığından sildiğimiz için, yığının bitiminden hemen sonra öğeyi saklayabileceğimiz her zaman açık bir nokta vardır. Öbek sıralaması, bir sonraki en büyük öğeyi art arda kaldırarak ve son konumda başlayıp öne doğru hareket ederek diziye koyarak sıralı bir sıraya ulaşır. Öbek türünde egemen olan bu son parçanın karmaşıklığıdır. Döngü şöyle:
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
Açıkçası, döngü O (n) kez çalışır ( kesin olarak n - 1 , son öğe zaten yerinde). deleteMax
Bir yığın için karmaşıklığı O (log n) 'dir . Genellikle kök (yığın içinde kalan en büyük öğe) kaldırılarak ve bir yaprak olan öbekteki son öğe ve dolayısıyla en küçük öğelerden biri ile değiştirilerek uygulanır. Bu yeni kök neredeyse heap özelliğini ihlal edecektir, bu yüzden siftDown
kabul edilebilir bir konuma geri taşıyana kadar aramalısınız . Bu, bir sonraki en büyük öğeyi köküne taşıma etkisine de sahiptir. buildHeap
Düğümlerin çoğunun siftDown
ağacın altından nereye çağırdığımızın aksine , şimdi siftDown
her yinelemede ağacın tepesinden aradığımızı fark edin!Ağaç küçülmesine rağmen, yeterince hızlı küçülmez : Ağacın yüksekliği, düğümlerin ilk yarısını kaldırana kadar (alt katmanı tamamen temizlediğinizde) sabit kalır. Sonra bir sonraki çeyrek için yükseklik h - 1'dir . Yani bu ikinci aşama için toplam çalışma
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
Düğmeye dikkat edin: şimdi sıfır çalışma durumu tek bir düğüme karşılık gelir ve h çalışma durumu düğümlerin yarısına karşılık gelir. Bu toplam, siftUp kullanılarak uygulanan verimsiz sürümü gibi O (n log n) 'buildHeap
dir. Ancak bu durumda, sıralamaya çalıştığımız için bir seçeneğimiz yok ve bir sonraki en büyük öğenin kaldırılmasını istiyoruz.
Özetle, yığın sıralaması için yapılan çalışma iki aşamanın toplamıdır: O (n) buildHeap ve O (n log n) için her düğümü sırayla kaldırma zamanıdır , dolayısıyla karmaşıklık O (n log n) olur . Karşılaştırma tabanlı bir sıralama için O (n log n) ' in yine de umabileceğiniz en iyisi olduğunu kanıtlayabilirsiniz ( bu yüzden hayal kırıklığına uğramanız veya yığın sıralamasının O (n) zaman sınırlaması buildHeap
yapar.