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 siftUpve siftDown. O (n) performansını elde etmek için siftUpve 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.buildHeapbuildHeapheapSortbuildHeapheapSortsiftDownsiftUp
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 siftDownve siftUpdüğü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 siftUpaltı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.
buildHeapHepsi 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 buildHeapkullanmak için alabileceği iki yaklaşım vardır .siftUpsiftDown
Yığının üstünden (dizinin başlangıcından) başlayın ve siftUpher öğ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 buildHeapdaha 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 . siftDownYaklaşı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, siftUpher 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 .
siftDownGerç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?
buildHeapDoğ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ı buildHeapgerektiren 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). deleteMaxBir 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 siftDownkabul 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. buildHeapDüğümlerin çoğunun siftDownağacın altından nereye çağırdığımızın aksine , şimdi siftDownher 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ı buildHeapyapar.