Dapatkah seseorang membantu menjelaskan bagaimana membangun tumpukan menjadi O (n) kompleksitas?
Memasukkan item ke tumpukan adalah O(log n)
, dan memasukkan diulang n / 2 kali (sisanya adalah daun, dan tidak dapat melanggar properti tumpukan). Jadi, ini berarti kerumitannya O(n log n)
, saya pikir.
Dengan kata lain, untuk setiap item yang kami "timbihkan", ia berpotensi untuk menyaring satu kali untuk setiap level untuk tumpukan sejauh ini (yang merupakan level log n).
Apa yang saya lewatkan?
Jawaban:
Saya pikir ada beberapa pertanyaan yang terkubur dalam topik ini:
buildHeap
sehingga berjalan dalam waktu O (n) ?buildHeap
berjalan dalam O (n) waktu ketika diimplementasikan dengan benar?Bagaimana Anda menerapkannya
buildHeap
sehingga berjalan dalam waktu O (n) ?Seringkali, jawaban atas pertanyaan-pertanyaan ini fokus pada perbedaan antara
siftUp
dansiftDown
. Membuat pilihan yang tepat antarasiftUp
dansiftDown
sangat penting untuk mendapatkan kinerja O (n)buildHeap
, tetapi tidak melakukan apa pun untuk membantu orang memahami perbedaan antarabuildHeap
danheapSort
secara umum. Memang, implementasi yang tepat dari keduanyabuildHeap
dan 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.siftDown
siftUp
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
siftDown
dansiftUp
sebanding dengan jarak node mungkin harus bergerak. SebabsiftDown
, itu jarak ke bagian bawah pohon, jadisiftDown
mahal untuk node di bagian atas pohon. DengansiftUp
, pekerjaan sebanding dengan jarak ke puncak pohon, jadisiftUp
mahal 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 memilihsiftDown
lebihsiftUp
.The
buildHeap
Fungsi mengambil array dari item disortir dan bergerak mereka sampai mereka semua memenuhi properti heap, sehingga menghasilkan tumpukan valid. Ada dua pendekatan yang mungkin diambil untukbuildHeap
menggunakansiftUp
dansiftDown
operasi yang telah kami jelaskan.Mulai di bagian atas tumpukan (awal array) dan panggil
siftUp
setiap 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
buildHeap
yang 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
siftDown
pendekatan diberikan oleh penjumlahanSetiap 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
siftUp
setiap node adalahHarus 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
siftDown
pendekatan 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:
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
buildHeap
dalam waktu linier, mengapa sortir heap membutuhkan waktu O (n log n) ? Nah, heap sort terdiri dari dua tahap. Pertama, kita memanggilbuildHeap
array, 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:Jelas, loop menjalankan O (n) kali ( n - 1 tepatnya, item terakhir sudah ada di tempatnya). Kompleksitas
deleteMax
untuk 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 meneleponsiftDown
sampai Anda memindahkannya kembali ke posisi yang dapat diterima. Ini juga memiliki efek memindahkan item terbesar berikutnya ke root. Perhatikan bahwa, berbeda denganbuildHeap
tempat sebagian besar simpul yang kita panggilsiftDown
dari bawah pohon, kita sekarang memanggilsiftDown
dari 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 adalahPerhatikan 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
buildHeap
yang 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
buildHeap
berlaku.sumber
siftUp
setiap item atau mulai dari akhir bergerak mundur dan memanggilsiftDown
. Apa pun pendekatan yang Anda pilih, Anda memilih item berikutnya di bagian array yang tidak disortir dan melakukan operasi yang sesuai untuk memindahkannya ke posisi yang valid di bagian array yang dipesan. Satu-satunya perbedaan adalah kinerja.Analisis Anda benar. Namun, tidak ketat.
Tidak mudah menjelaskan mengapa membangun heap adalah operasi linier, Anda sebaiknya membacanya.
Sebuah analisis yang besar dari algoritma dapat dilihat di sini .
Gagasan utamanya adalah bahwa dalam
build_heap
algoritma,heapify
biaya sebenarnya bukanO(log n)
untuk semua elemen.Ketika
heapify
dipanggil, waktu berjalan tergantung pada seberapa jauh suatu elemen mungkin bergerak turun di pohon sebelum proses berakhir. Dengan kata lain, itu tergantung pada ketinggian elemen di heap. Dalam kasus terburuk, elemen mungkin turun sampai ke tingkat daun.Mari kita hitung pekerjaan yang dilakukan level demi level.
Di level paling bawah, ada
2^(h)
node, tapi kami tidak memanggilheapify
semua ini, jadi pekerjaannya adalah 0. Pada level berikutnya ada2^(h − 1)
node, dan masing-masing mungkin turun 1 level. Di level 3 dari bawah, ada2^(h − 2)
node, dan masing-masing mungkin turun 2 level.Seperti yang Anda lihat tidak semua operasi heapify
O(log n)
, inilah mengapa Anda mendapatkannyaO(n)
.sumber
Heapify
adalahO(n)
ketika dilakukan dengansiftDown
tetapiO(n log n)
saat dilakukan dengansiftUp
. Penyortiran yang sebenarnya (menarik item dari tumpukan satu per satu) harus dilakukan dengansiftUp
demikian adalah karena ituO(n log n)
.i
dari bawah pohon yang tinggi h harus membuat2* log(h-i)
perbandingan juga dan harus diperhitungkan juga @ The111. Bagaimana menurut anda?Secara intuitif:
Tidak terlalu. Logika Anda tidak menghasilkan ikatan yang ketat - logika ini memperkirakan kerumitan setiap tumpukan. Jika dibangun dari bawah ke atas, penyisipan (heapify) bisa menjadi jauh lebih sedikit
O(log(n))
. Prosesnya adalah sebagai berikut:(Langkah 1) Elemen pertama
n/2
pergi di baris bawah tumpukan.h=0
, jadi heapify tidak diperlukan.(Langkah 2) Elemen berikutnya berjalan di baris 1 ke atas dari bawah. , heapify filter 1 level ke bawah.
n/22
h=1
(Langkah i ) Elemen berikutnya berjalan berurutan dari bawah. , naikkan tingkat filter ke bawah.
n/2i
i
h=i
i
(Langkah log (n) ) Elemen terakhir berjalan berturut - turut dari bawah. , naikkan tingkat filter ke bawah.
n/2log2(n) = 1
log(n)
h=log(n)
log(n)
PEMBERITAHUAN: bahwa setelah langkah pertama,
1/2
elemen(n/2)
sudah ada di heap, dan kami bahkan tidak perlu memanggil heapify sekali. Juga, perhatikan bahwa hanya satu elemen, root, yang sebenarnya menimbulkanlog(n)
kompleksitas penuh .Secara teoretis:
Langkah-langkah Total
N
untuk membangun tumpukan ukurann
, dapat ditulis secara matematis.Pada ketinggian
i
, kami telah menunjukkan (di atas) bahwa akan ada elemen yang perlu disebut heapify, dan kami tahu heapify pada ketinggian itu . Ini memberi:n/2i+1
i
O(i)
Solusi untuk penjumlahan terakhir dapat ditemukan dengan mengambil turunan dari kedua sisi dari persamaan deret geometri yang terkenal:
Akhirnya, memasukkan
x = 1/2
ke dalam persamaan menghasilkan di atas2
. Memasukkan ini ke persamaan pertama memberi:Dengan demikian, jumlah langkah adalah ukuran
O(n)
sumber
Ini akan menjadi O (n log n) jika Anda membangun heap dengan memasukkan elemen berulang kali. Namun, Anda dapat membuat tumpukan baru lebih efisien dengan memasukkan elemen-elemen dalam urutan sewenang-wenang dan kemudian menerapkan algoritma untuk "menumpuk" mereka ke dalam urutan yang tepat (tergantung pada jenis tumpukan tentu saja).
Lihat http://en.wikipedia.org/wiki/Binary_heap , "Membangun heap" sebagai contoh. Dalam hal ini Anda pada dasarnya bekerja dari tingkat bawah pohon, bertukar simpul induk dan anak hingga kondisi tumpukan terpenuhi.
sumber
Sudah ada beberapa jawaban bagus tetapi saya ingin menambahkan sedikit penjelasan visual
Sekarang, lihat gambar, ada
n/2^1
simpul hijau dengan tinggi 0 (di sini 23/2 = 12)n/2^2
simpul merah dengan tinggi 1 (di sini 23/4 = 6)n/2^3
simpul biru dengan tinggi 2 (di sini 23/8 = 3)n/2^4
simpul ungu dengan tinggi 3 (di sini 23/16 = 2)sehingga ada
n/2^(h+1)
simpul untuk tinggi hUntuk menemukan kompleksitas waktu mari kita hitung jumlah pekerjaan yang dilakukan atau maks tidak dari iterasi yang dilakukan oleh setiap node
sekarang dapat diperhatikan bahwa setiap node dapat melakukan iterasi (paling jauh) == ketinggian node
jadi untuk setiap node dengan tinggi h pekerjaan maksimum yang dilakukan adalah n / 2 ^ (h + 1) * h
Sekarang total pekerjaan yang dilakukan adalah
sekarang untuk nilai h , urutannya
tidak akan pernah melebihi 1
Dengan demikian kompleksitas waktu tidak akan pernah melebihi O (n) untuk membangun timbunan
sumber
Seperti yang kita ketahui ketinggian heap adalah log (n) , di mana n adalah jumlah total elemen. Mari kita menggambarkannya sebagai h
Ketika kita melakukan operasi heapify, maka elemen pada level terakhir ( h ) tidak akan bergerak bahkan satu pun langkah.
Jumlah elemen pada level terakhir kedua ( h-1 ) adalah 2 jam-1 dan mereka dapat bergerak pada level maksimal 1 (selama heapify).
Demikian pula, untuk i th , tingkat kita memiliki 2 i elemen yang dapat bergerak hi posisi.
Oleh karena itu jumlah total gerakan = S = 2 jam * 0 + 2 jam-1 * 1 + 2 jam-2 * 2 + ... 2 0 * jam
S = 2 jam {1/2 + 2/2 2 + 3/2 3 + ... jam / 2 jam } ----------------------- -------------------------- 1
ini adalah seri AGP , untuk menyelesaikan ini bagi kedua belah pihak dengan 2
S / 2 = 2 jam {1/2 2 + 2/2 3 + ... h / 2 h + 1 } --------------------------------- ---------------- 2
pengurangan persamaan 2 dari 1 menghasilkan
S / 2 = 2 jam { 1/2 + 1/2 2 + 1/2 3 + ... + 1 / 2 jam + h / 2 jam + 1 }
S = 2 h + 1 {1/2 + 1/2 2 + 1/2 3 + ... + 1/2 h + h / 2 h + 1 }
sekarang 1/2 + 1/2 2 + 1/2 3 + ... + 1/2 jam adalah penurunan GP yang jumlahnya kurang dari 1 (ketika h cenderung tak terhingga, jumlahnya cenderung ke 1). Dalam analisis lebih lanjut, mari kita ambil batas atas jumlah yaitu 1.
Ini memberi S = 2 jam + 1 {1 + jam / 2 jam + 1 }
= 2 jam + 1 + jam
~ 2 jam + jam
sebagai h = log (n) , 2 jam = n
Oleh karena itu S = n + log (n)
T (C) = O (n)
sumber
Saat membangun tumpukan, katakanlah Anda mengambil pendekatan dari bawah ke atas.
sumber
Dalam hal membangun heap, kita mulai dari ketinggian, logn -1 (di mana logn adalah ketinggian pohon elemen n). Untuk setiap elemen yang hadir pada ketinggian 'h', kita turun pada ketinggian max upto (logn -h).
sumber
Penyisipan yang berurutan dapat dijelaskan dengan:
Oleh
n! =~ O(n^(n + O(1)))
karena itu, pendekatan StarlingT =~ O(nlog(n))
Semoga ini bisa membantu, cara optimal
O(n)
menggunakan algoritma build heap untuk himpunan tertentu (pemesanan tidak masalah).sumber
Pada dasarnya, pekerjaan dilakukan hanya pada node non-daun sambil membangun heap ... dan pekerjaan yang dilakukan adalah jumlah swapping untuk memenuhi kondisi heap ... dengan kata lain (dalam kasus terburuk) jumlahnya sebanding dengan tinggi dari node ... semuanya kompleksitas masalah sebanding dengan jumlah ketinggian semua node non-daun .. yang (2 ^ h + 1 - 1) -h-1 = nh-1 = Di)
sumber
@ bcorso telah menunjukkan bukti analisis kompleksitas. Namun demi analisis kompleksitas yang masih dipelajari, saya ingin menambahkan ini:
Dasar dari kesalahan awal Anda adalah karena salah tafsir dari makna pernyataan, "memasukkan ke tumpukan membutuhkan O (log n) waktu". Memasukkan ke dalam tumpukan memang O (log n), tetapi Anda harus mengenali bahwa n adalah ukuran tumpukan selama penyisipan .
Dalam konteks memasukkan n objek ke dalam tumpukan, kompleksitas dari penyisipan ke-i adalah O (log n_i) di mana n_i adalah ukuran tumpukan seperti pada saat penyisipan i. Hanya penyisipan terakhir yang memiliki kompleksitas O (log n).
sumber
Mari kita anggap Anda memiliki elemen N di tumpukan. Maka tingginya akan menjadi Log (N)
Sekarang Anda ingin memasukkan elemen lain, maka kompleksitasnya adalah: Log (N) , kita harus membandingkan semua jalan UP ke root.
Sekarang Anda memiliki N + 1 elemen & tinggi = Log (N + 1)
Menggunakan teknik induksi dapat dibuktikan bahwa kompleksitas penyisipan akan menjadi logo .
Sekarang menggunakan
Ini disederhanakan menjadi: ∑logi = log (n!)
yang sebenarnya O (NlogN)
Tapi
kami melakukan sesuatu yang salah di sini, karena dalam semua kasus kami tidak mencapai di atas. Karena itu ketika menjalankan sebagian besar waktu kita mungkin menemukan itu, kita bahkan tidak akan naik setengah pohon. Dari mana, ikatan ini dapat dioptimalkan untuk memiliki ikatan lain yang lebih ketat dengan menggunakan matematika yang diberikan dalam jawaban di atas.
Kesadaran ini datang kepada saya setelah detail & percobaan pada tumpukan.
sumber
Saya sangat suka penjelasan oleh Jeremy west .... pendekatan lain yang sangat mudah dipahami diberikan di sini http://courses.washington.edu/css343/zander/NotesProbs/heapcomplexity
karena, buildheap tergantung menggunakan tergantung pada heapify dan pendekatan shiftdown digunakan yang tergantung pada jumlah ketinggian semua node. Jadi, untuk menemukan jumlah tinggi node yang diberikan oleh S = penjumlahan dari i = 0 ke i = h dari (2 ^ i * (hi)), di mana h = logn adalah ketinggian penyelesaian pohon s, kita dapatkan s = 2 ^ (h + 1) - 1 - (h + 1) karena, n = 2 ^ (h + 1) - 1 s = n - h - 1 = n-logn - 1 s = O (n), dan kompleksitas buildheap adalah O (n).
sumber
"Batas waktu linier build Heap, dapat ditunjukkan dengan menghitung jumlah ketinggian semua node di heap, yang merupakan jumlah maksimum garis putus-putus. Untuk pohon biner sempurna tinggi h berisi N = 2 ^ ( h + 1) - 1 node, jumlah dari ketinggian node adalah N - H - 1. Dengan demikian itu adalah O (N). "
sumber
Bukti O (n)
Buktinya tidak mewah, dan cukup mudah, saya hanya membuktikan kasus untuk pohon biner penuh, hasilnya bisa digeneralisasi untuk pohon biner lengkap.
sumber
Kita mendapatkan runtime untuk heap build dengan mencari tahu langkah maksimum yang dapat diambil setiap node. Jadi kita perlu tahu berapa banyak node di setiap baris dan seberapa jauh dari masing-masing node.
Mulai dari simpul akar setiap baris berikutnya memiliki dua kali lipat node daripada baris sebelumnya, jadi dengan menjawab seberapa sering kita dapat menggandakan jumlah node sampai kita tidak memiliki node yang tersisa kita mendapatkan ketinggian pohon. Atau dalam istilah matematika ketinggian pohon adalah log2 (n), n menjadi panjang array.
Untuk menghitung node dalam satu baris kita mulai dari belakang, kita tahu n / 2 node berada di bagian bawah, jadi dengan membagi 2 kita mendapatkan baris sebelumnya dan seterusnya.
Berdasarkan ini kita mendapatkan formula ini untuk pendekatan Siftdown: (0 * n / 2) + (1 * n / 4) + (2 * n / 8) + ... + (log2 (n) * 1)
Istilah dalam paranthesis terakhir adalah ketinggian pohon dikalikan dengan satu simpul yang ada di akar, istilah dalam paranthesis pertama adalah semua simpul di baris bawah dikalikan dengan panjangnya, 0. Formula yang sama dalam cerdas:
Membawa n kembali kita memiliki 2 * n, 2 dapat dibuang karena itu konstan dan tada kita memiliki runtime kasus terburuk dari pendekatan Siftdown: n.
sumber
pikir kamu membuat kesalahan. Lihatlah ini: http://golang.org/pkg/container/heap/ Membangun heap bukan O (n). Namun, memasukkan adalah O (lg (n). Saya mengasumsikan inisialisasi adalah O (n) jika Anda menetapkan ukuran tumpukan b / c tumpukan perlu mengalokasikan ruang dan mengatur struktur data. Jika Anda memiliki n item untuk dimasukkan ke heap maka ya, setiap insert adalah lg (n) dan ada item n, jadi Anda mendapatkan n * lg (n) seperti yang dinyatakan
sumber