Saya pikir ada beberapa pertanyaan yang terkubur dalam topik ini:
- Bagaimana Anda menerapkannya
buildHeapsehingga berjalan dalam waktu O (n) ?
- Bagaimana Anda menunjukkan bahwa
buildHeapberjalan dalam O (n) waktu ketika diimplementasikan dengan benar?
- Mengapa logika yang sama itu tidak berfungsi untuk membuat heap sort berjalan pada waktu O (n) daripada O (n log n) ?
Bagaimana Anda menerapkannya buildHeapsehingga berjalan dalam waktu O (n) ?
Seringkali, jawaban atas pertanyaan-pertanyaan ini fokus pada perbedaan antara siftUpdan siftDown. Membuat pilihan yang tepat antara siftUpdan siftDownsangat penting untuk mendapatkan kinerja O (n)buildHeap , tetapi tidak melakukan apa pun untuk membantu orang memahami perbedaan antara buildHeapdan heapSortsecara umum. Memang, implementasi yang tepat dari keduanya buildHeapdan hanyaheapSort akan digunakan . The Operasi hanya dibutuhkan untuk melakukan sisipan ke dalam tumpukan yang ada, sehingga akan digunakan untuk mengimplementasikan antrian prioritas menggunakan tumpukan biner, misalnya.siftDownsiftUp
Saya telah menulis ini untuk menjelaskan cara kerja tumpukan maks. Ini adalah tipe tumpukan yang biasanya digunakan untuk mengurutkan tumpukan atau untuk antrian prioritas di mana nilai yang lebih tinggi menunjukkan prioritas yang lebih tinggi. Tumpukan min juga berguna; misalnya, saat mengambil item dengan kunci integer dalam urutan menaik atau string dalam urutan abjad. Prinsip-prinsipnya persis sama; cukup alihkan urutan.
The properti tumpukan menetapkan bahwa setiap node dalam tumpukan biner harus setidaknya sama besar dengan kedua anak-anaknya. Secara khusus, ini menyiratkan bahwa item terbesar di heap adalah di root. Memilah ke bawah dan memilah pada dasarnya adalah operasi yang sama di arah yang berlawanan: pindahkan simpul yang menyinggung sampai memenuhi properti tumpukan:
siftDown menukar simpul yang terlalu kecil dengan anak terbesarnya (dengan demikian memindahkannya ke bawah) hingga setidaknya sama besar dengan kedua simpul di bawahnya.
siftUp menukar simpul yang terlalu besar dengan induknya (dengan demikian memindahkannya ke atas) sampai tidak lebih besar dari simpul di atasnya.
Jumlah operasi yang diperlukan untuk siftDowndan siftUpsebanding dengan jarak node mungkin harus bergerak. Sebab siftDown, itu jarak ke bagian bawah pohon, jadi siftDownmahal untuk node di bagian atas pohon. Dengan siftUp, pekerjaan sebanding dengan jarak ke puncak pohon, jadi siftUpmahal untuk simpul di bagian bawah pohon. Meskipun kedua operasi adalah O (log n) dalam kasus terburuk, dalam heap, hanya satu node di bagian atas sedangkan setengah dari node terletak di lapisan bawah. Jadi seharusnya tidak terlalu mengejutkan bahwa jika kita harus menerapkan operasi ke setiap node, kita akan memilih siftDownlebih siftUp.
The buildHeapFungsi mengambil array dari item disortir dan bergerak mereka sampai mereka semua memenuhi properti heap, sehingga menghasilkan tumpukan valid. Ada dua pendekatan yang mungkin diambil untuk buildHeapmenggunakan siftUpdan siftDownoperasi yang telah kami jelaskan.
Mulai di bagian atas tumpukan (awal array) dan panggil siftUpsetiap item. Pada setiap langkah, item yang diayak sebelumnya (item sebelum item saat ini dalam array) membentuk heap yang valid, dan menyaring item berikutnya menempatkannya ke posisi yang valid di heap. Setelah menyaring setiap node, semua item memenuhi properti heap.
Atau, pergi ke arah yang berlawanan: mulai dari ujung array dan bergerak mundur ke arah depan. Pada setiap iterasi, Anda menyaring suatu barang sampai berada di lokasi yang benar.
Untuk implementasi mana buildHeapyang lebih efisien?
Kedua solusi ini akan menghasilkan tumpukan yang valid. Tidak mengherankan, yang lebih efisien adalah operasi kedua yang digunakan siftDown.
Biarkan h = log n mewakili ketinggian tumpukan. Pekerjaan yang dibutuhkan untuk siftDownpendekatan diberikan oleh penjumlahan
(0 * n/2) + (1 * n/4) + (2 * n/8) + ... + (h * 1).
Setiap istilah dalam penjumlahan memiliki jarak maksimum, sebuah simpul pada ketinggian tertentu harus bergerak (nol untuk lapisan bawah, h untuk root) dikalikan dengan jumlah simpul pada ketinggian itu. Sebaliknya, jumlah untuk memanggil siftUpsetiap node adalah
(h * n/2) + ((h-1) * n/4) + ((h-2)*n/8) + ... + (0 * 1).
Harus jelas bahwa jumlah kedua lebih besar. Istilah pertama saja adalah hn / 2 = 1/2 n log n , jadi pendekatan ini memiliki kompleksitas paling baik O (n log n) .
Bagaimana kita membuktikan bahwa jumlah untuk siftDownpendekatan itu memang O (n) ?
Salah satu metode (ada analisis lain yang juga berfungsi) adalah mengubah jumlah terbatas menjadi seri tak terbatas dan kemudian menggunakan seri Taylor. Kami dapat mengabaikan istilah pertama, yaitu nol:

Jika Anda tidak yakin mengapa masing-masing langkah itu berhasil, berikut ini adalah pembenaran untuk proses ini dengan kata-kata:
- Persyaratannya semua positif, sehingga jumlah yang terbatas harus lebih kecil dari jumlah yang tak terbatas.
- Seri ini sama dengan seri daya yang dievaluasi pada x = 1/2 .
- Rangkaian daya itu sama dengan (waktu konstan) turunan dari deret Taylor untuk f (x) = 1 / (1-x) .
- x = 1/2 berada dalam interval konvergensi dari seri Taylor itu.
- Oleh karena itu, kita dapat mengganti seri Taylor dengan 1 / (1-x) , membedakan, dan mengevaluasi untuk menemukan nilai seri infinite.
Karena jumlah tak terhingga tepat n , kami menyimpulkan bahwa jumlah terbatas tidak lebih besar, dan karenanya, O (n) .
Mengapa tumpukan membutuhkan waktu O (n log n) ?
Jika dimungkinkan untuk berjalan buildHeapdalam waktu linier, mengapa sortir heap membutuhkan waktu O (n log n) ? Nah, heap sort terdiri dari dua tahap. Pertama, kita memanggil buildHeaparray, yang membutuhkan waktu O (n) jika diterapkan secara optimal. Tahap selanjutnya adalah berulang kali menghapus item terbesar di heap dan meletakkannya di akhir array. Karena kami menghapus item dari heap, selalu ada tempat terbuka tepat setelah akhir heap di mana kami dapat menyimpan item. Jadi heap sort mencapai urutan diurutkan dengan secara berturut-turut menghapus item terbesar berikutnya dan memasukkannya ke dalam array mulai dari posisi terakhir dan bergerak ke arah depan. Kompleksitas dari bagian terakhir inilah yang mendominasi dalam heap sort. Loop terlihat seperti ini:
for (i = n - 1; i > 0; i--) {
arr[i] = deleteMax();
}
Jelas, loop menjalankan O (n) kali ( n - 1 tepatnya, item terakhir sudah ada di tempatnya). Kompleksitas deleteMaxuntuk heap adalah O (log n) . Ini biasanya diterapkan dengan menghapus root (item terbesar yang tersisa di heap) dan menggantinya dengan item terakhir di heap, yang merupakan daun, dan karena itu salah satu item terkecil. Root baru ini hampir pasti akan melanggar properti heap, jadi Anda harus menelepon siftDownsampai Anda memindahkannya kembali ke posisi yang dapat diterima. Ini juga memiliki efek memindahkan item terbesar berikutnya ke root. Perhatikan bahwa, berbeda dengan buildHeaptempat sebagian besar simpul yang kita panggil siftDowndari bawah pohon, kita sekarang memanggil siftDowndari atas pohon pada setiap iterasi!Meskipun pohon menyusut, ia tidak menyusut cukup cepat : Ketinggian pohon tetap konstan sampai Anda telah menghapus setengah bagian pertama dari simpul (ketika Anda membersihkan lapisan bawah sepenuhnya). Kemudian untuk kuartal berikutnya, ketinggiannya adalah h - 1 . Jadi total pekerjaan untuk tahap kedua ini adalah
h*n/2 + (h-1)*n/4 + ... + 0 * 1.
Perhatikan sakelar: sekarang kasus kerja nol berhubungan dengan satu node dan kasus kerja h sesuai dengan setengah node. Jumlah ini adalah O (n log n) sama seperti versi tidak efisien buildHeapyang diimplementasikan menggunakan siftUp. Tetapi dalam kasus ini, kami tidak punya pilihan karena kami mencoba menyortir dan kami membutuhkan item terbesar berikutnya dihapus berikutnya.
Singkatnya, pekerjaan untuk heap sort adalah jumlah dari dua tahap: O (n) waktu untuk buildHeap dan O (n log n) untuk menghapus setiap node secara berurutan , sehingga kompleksitasnya adalah O (n log n) . Anda dapat membuktikan (menggunakan beberapa ide dari teori informasi) bahwa untuk jenis berbasis perbandingan, O (n log n) adalah yang terbaik yang bisa Anda harapkan, jadi tidak ada alasan untuk kecewa dengan ini atau mengharapkan tumpukan tumpukan untuk mencapai O (n) batas waktu yang buildHeapberlaku.