Mengapa std::map
diimplementasikan sebagai pohon merah-hitam ?
Ada beberapa pohon pencarian biner seimbang (BST) di luar sana. Apa trade-off desain dalam memilih pohon merah-hitam?
c++
dictionary
data-structures
stl
binary-search-tree
Denis Gorodetskiy
sumber
sumber
O(logn)
dan tidakO(1)
, tetapi nilainya akan selalu diurutkan. Mulai dari C ++ 11 (saya pikir), adaunordered_map
danunordered_set
, yang diimplementasikan menggunakan fungsi hash dan sementara mereka tidak diurutkan, sebagian besar kueri dan operasi dimungkinkan dalamO(1)
(rata-rata)Jawaban:
Mungkin dua algoritma pohon penyeimbang diri yang paling umum adalah pohon Merah-Hitam dan pohon AVL . Untuk menyeimbangkan pohon setelah penyisipan / perbarui kedua algoritma gunakan gagasan rotasi di mana simpul pohon diputar untuk melakukan penyeimbangan kembali.
Sementara di kedua algoritma operasi insert / delete adalah O (log n), dalam kasus rotasi penyeimbangan ulang pohon Merah-Hitam adalah operasi O (1) sementara dengan AVL ini adalah operasi O (log n) , membuat Pohon Merah-Hitam lebih efisien dalam aspek tahap penyeimbangan ulang dan salah satu alasan yang mungkin lebih sering digunakan.
Pohon Merah-Hitam digunakan di sebagian besar koleksi perpustakaan, termasuk penawaran dari Java dan Microsoft .NET Framework.
sumber
std::map
implementasi, melacak pengembang, dan bertanya kepada mereka kriteria apa yang mereka gunakan untuk membuat keputusan, jadi ini tetap spekulasi.Itu benar-benar tergantung penggunaannya. Pohon AVL biasanya memiliki rotasi rebalancing yang lebih banyak. Jadi, jika aplikasi Anda tidak memiliki terlalu banyak operasi penyisipan dan penghapusan, tetapi beban berat pada pencarian, maka pohon AVL mungkin adalah pilihan yang baik.
std::map
menggunakan pohon Merah-Hitam karena mendapat trade-off yang wajar antara kecepatan penyisipan / penghapusan simpul dan pencarian.sumber
Pohon AVL memiliki ketinggian maksimum 1,44logn, sedangkan pohon RB memiliki maksimum 2logn. Memasukkan elemen dalam AVL dapat menyiratkan penyeimbangan kembali pada satu titik di pohon. Penyeimbangan ulang menyelesaikan penyisipan. Setelah menyisipkan daun baru, memperbarui leluhur daun itu harus dilakukan hingga ke akar, atau sampai pada titik di mana kedua sub pohon memiliki kedalaman yang sama. Probabilitas harus memperbarui k node adalah 1/3 ^ k. Penyeimbangan ulang adalah O (1). Menghapus elemen dapat menyiratkan lebih dari satu penyeimbangan ulang (hingga setengah kedalaman pohon).
BPR-pohon adalah B-pohon pesanan 4 yang direpresentasikan sebagai pohon pencarian biner. 4-simpul dalam B-tree menghasilkan dua level dalam BST yang setara. Dalam kasus terburuk, semua node pohon adalah 2-node, dengan hanya satu rantai 3-node ke daun. Daun itu akan berada pada jarak 2 logn dari akar.
Turun dari root ke titik penyisipan, kita harus mengubah 4-node menjadi 2-node, untuk memastikan setiap penyisipan tidak akan menjenuhkan daun. Kembali dari penyisipan, semua node ini harus dianalisis untuk memastikan mereka benar mewakili 4-node. Ini juga bisa dilakukan turun di pohon. Biaya global akan sama. Tidak ada makan siang gratis! Menghapus elemen dari pohon adalah urutan yang sama.
Semua pohon ini mengharuskan node membawa informasi tentang tinggi, berat, warna, dll. Hanya pohon Splay yang bebas dari info tambahan tersebut. Tetapi sebagian besar orang takut pada pohon Splay, karena struktur bangunannya yang bobrok!
Akhirnya, pohon juga dapat membawa informasi berat di simpul, memungkinkan penyeimbangan berat. Berbagai skema dapat diterapkan. Seseorang harus menyeimbangkan kembali ketika subtree mengandung lebih dari 3 kali jumlah elemen subtree lainnya. Penyeimbangan kembali dilakukan baik melalui rotasi tunggal atau ganda. Ini berarti kasus terburuk dari 2.4logn. Seseorang bisa lolos dengan 2 kali alih-alih 3, rasio yang jauh lebih baik, tetapi itu mungkin berarti meninggalkan sedikit kurang dari 1% dari subtree yang tidak seimbang di sana-sini. Rumit!
Jenis pohon apa yang terbaik? AVL pasti. Mereka adalah yang paling sederhana untuk dikodekan, dan memiliki ketinggian terburuk mereka yang terdekat dengan logn. Untuk pohon dengan 10.000.000 elemen, AVL paling tinggi 29, RB 40, dan bobot seimbang 36 atau 50 tergantung rasio.
Ada banyak variabel lain: keacakan, rasio penambahan, penghapusan, pencarian, dll.
sumber
Jawaban sebelumnya hanya membahas alternatif pohon dan merah hitam mungkin hanya tersisa karena alasan historis.
Mengapa bukan tabel hash?
Suatu tipe hanya membutuhkan
<
operator (perbandingan) untuk digunakan sebagai kunci dalam pohon. Namun, tabel hash mengharuskan setiap jenis kunci memilikihash
fungsi yang ditentukan. Menjaga persyaratan tipe menjadi minimum sangat penting untuk pemrograman generik sehingga Anda dapat menggunakannya dengan berbagai jenis dan algoritme.Merancang tabel hash yang baik membutuhkan pengetahuan yang mendalam tentang konteks yang akan digunakan. Haruskah itu menggunakan pengalamatan terbuka, atau menghubungkan rantai? Tingkat beban apa yang harus diterima sebelum mengubah ukuran? Haruskah menggunakan hash mahal yang menghindari tabrakan, atau yang kasar dan cepat?
Karena STL tidak dapat mengantisipasi yang merupakan pilihan terbaik untuk aplikasi Anda, standarnya harus lebih fleksibel. Pohon "hanya bekerja" dan skala baik.
(C ++ 11 memang menambahkan tabel hash dengan
unordered_map
. Anda dapat melihat dari dokumentasi itu memerlukan kebijakan pengaturan untuk mengkonfigurasi banyak opsi ini.)Bagaimana dengan pohon lain?
Pohon Merah Hitam menawarkan pencarian cepat dan menyeimbangkan diri, tidak seperti BST. Pengguna lain menunjukkan kelebihannya dibandingkan dengan pohon AVL penyeimbang sendiri.
Alexander Stepanov (Pencipta STL) mengatakan bahwa ia akan menggunakan Pohon B * alih-alih pohon Merah-Hitam jika ia menulis
std::map
lagi, karena lebih ramah untuk cache memori modern.Haruskah peta selalu menggunakan pohon?
Kemungkinan implementasi peta lainnya adalah vektor yang diurutkan (sortasi sort) dan pencarian biner. Ini akan bekerja dengan baik untuk kontainer yang tidak sering dimodifikasi tetapi sering ditanyai. Saya sering melakukan ini di C sebagai
qsort
danbsearch
dibangun.Apakah saya perlu menggunakan peta?
Pertimbangan cache berarti jarang digunakan
std::list
ataustd::deque
berlebihanstd:vector
bahkan untuk situasi yang kami diajarkan di sekolah (seperti menghapus elemen dari bagian tengah daftar). Menerapkan alasan yang sama, menggunakan loop untuk pencarian linear daftar seringkali lebih efisien dan lebih bersih daripada membangun peta untuk beberapa pencarian.Tentu saja memilih wadah yang mudah dibaca biasanya lebih penting daripada kinerja.
sumber
Pembaruan 2017-06-14: webbertiger mengedit jawabannya setelah saya berkomentar. Saya harus menunjukkan bahwa jawabannya sekarang jauh lebih baik di mata saya. Tapi saya tetap menjawab sebagai informasi tambahan ...
Karena saya pikir jawaban pertama salah (koreksi: tidak keduanya lagi) dan jawaban ketiga salah. Saya merasa saya harus mengklarifikasi hal-hal ...
2 pohon paling populer adalah AVL dan Red Black (RB). Perbedaan utama terletak pada pemanfaatan:
Perbedaan utama berasal dari pewarnaan. Anda memiliki aksi re-balance yang lebih sedikit dalam RB tree daripada AVL karena pewarnaan memungkinkan Anda untuk kadang-kadang melewati atau memperpendek aksi re-balance yang memiliki biaya relatif tinggi. Karena pewarnaannya, pohon RB juga memiliki level node yang lebih tinggi karena dapat menerima node merah di antara yang hitam (memiliki kemungkinan ~ 2x level lebih) membuat pencarian (baca) sedikit kurang efisien ... tetapi karena itu adalah konstan (2x), tetap di O (log n).
Jika Anda mempertimbangkan hit kinerja untuk modifikasi pohon (signifikansi) VS hit kinerja konsultasi pohon (hampir tidak signifikan), menjadi alami untuk memilih RB daripada AVL untuk kasus umum.
sumber
Ini hanya pilihan implementasi Anda - mereka dapat diimplementasikan sebagai pohon seimbang. Berbagai pilihan semuanya sebanding dengan perbedaan kecil. Karena itu, apapun itu sama baiknya dengan apapun.
sumber