Binary Tree di sini mungkin belum tentu menjadi Binary Search Tree.
Struktur dapat diambil sebagai -
struct node {
int data;
struct node *left;
struct node *right;
};
Solusi maksimal yang bisa saya selesaikan dengan seorang teman adalah sesuatu seperti ini -
Pertimbangkan pohon biner ini :
Invers traversal menghasilkan - 8, 4, 9, 2, 5, 1, 6, 3, 7
Dan hasil postorder traversal - 8, 9, 4, 5, 2, 6, 7, 3, 1
Jadi misalnya, jika kita ingin menemukan leluhur umum dari node 8 dan 5, maka kita membuat daftar semua node yang berada di antara 8 dan 5 di traversal pohon inorder, yang dalam hal ini kebetulan [4, 9 , 2]. Kemudian kami memeriksa simpul mana dalam daftar ini yang muncul terakhir dalam postorder traversal, yaitu 2. Oleh karena itu leluhur bersama untuk 8 dan 5 adalah 2.
Kompleksitas untuk algoritma ini, saya percaya adalah O (n) (O (n) untuk travers inorder / postorder, sisa langkah lagi menjadi O (n) karena mereka tidak lebih dari iterasi sederhana dalam array). Tetapi ada peluang kuat bahwa ini salah. :-)
Tapi ini pendekatan yang sangat kasar, dan saya tidak yakin apakah itu rusak untuk beberapa kasus. Apakah ada solusi lain (mungkin lebih optimal) untuk masalah ini?
Jawaban:
Nick Johnson benar bahwa algoritma kompleksitas waktu O (n) adalah yang terbaik yang dapat Anda lakukan jika Anda tidak memiliki orangtua pointer.) Untuk versi rekursif sederhana dari algoritma tersebut lihat kode di posting Kinding yang berjalan dalam waktu O (n) waktu .
Tetapi perlu diingat bahwa jika node Anda memiliki pointer orangtua, algoritma yang ditingkatkan dimungkinkan. Untuk kedua node yang dimaksud buatlah daftar yang berisi path dari root ke node dengan mulai dari node, dan masukkan induk ke depan.
Jadi untuk 8 dalam contoh Anda, Anda mendapatkan (menunjukkan langkah-langkah): {4}, {2, 4}, {1, 2, 4}
Lakukan hal yang sama untuk node Anda yang lain dalam pertanyaan, menghasilkan (langkah-langkah tidak ditampilkan): {1, 2}
Sekarang bandingkan dua daftar yang Anda buat mencari elemen pertama di mana daftar berbeda, atau elemen terakhir dari salah satu daftar, mana yang lebih dulu.
Algoritma ini membutuhkan waktu O (h) di mana h adalah ketinggian pohon. Dalam kasus terburuk O (h) setara dengan O (n), tetapi jika pohon seimbang, itu hanya O (log (n)). Ini juga membutuhkan ruang O (h). Versi yang disempurnakan adalah mungkin yang hanya menggunakan ruang konstan, dengan kode yang ditunjukkan pada posting CEGRD
Terlepas dari bagaimana pohon dibangun, jika ini akan menjadi operasi yang Anda lakukan berkali-kali pada pohon tanpa mengubahnya di antara, ada algoritma lain yang dapat Anda gunakan yang memerlukan persiapan waktu O (n) [linear], tetapi kemudian menemukan pasangan hanya membutuhkan waktu O (1) [konstan]. Untuk referensi algoritma ini, lihat halaman masalah leluhur umum terendah di Wikipedia . (Kredit ke Jason untuk awalnya memposting tautan ini)
sumber
O(h)
hanyaO(log(n))
jika pohonnya seimbang. Untuk pohon apa pun, baik itu biner atau tidak, jika Anda memiliki pointer orangtua, Anda dapat menentukan jalur dari sebuah daun ke root dalamO(h)
waktu, cukup dengan mengikuti pointer orangtua hinggah
waktu. Itu memberi Anda jalan dari daun ke akar. Jika jalur disimpan sebagai tumpukan, maka iterasi tumpukan memberi Anda jalan dari root ke daun. Jika Anda kekurangan pointer orangtua, dan tidak memiliki struktur khusus ke pohon, maka menemukan jalan dari akar ke daun memang membutuhkanO(n)
waktu.Mulai dari
root
node dan bergerak ke bawah jika Anda menemukan node yang memilikip
atauq
sebagai anak langsung maka itu adalah LCA. (edit - ini seharusnya jikap
atauq
merupakan nilai node, kembalikan. Jika tidak, akan gagal ketika salah satup
atauq
merupakan anak langsung dari yang lain.)Lain jika Anda menemukan node dengan
p
subtree kanan (atau kiri) danq
kiri (atau kanan) maka itu adalah LCA.Kode tetap terlihat seperti:
Kode di bawah ini gagal ketika salah satunya adalah anak langsung dari yang lain.
Kode Beraksi
sumber
Berikut adalah kode yang berfungsi di JAWA
sumber
Jawaban yang diberikan sejauh ini menggunakan rekursi atau menyimpan, misalnya, jalur di memori.
Kedua pendekatan ini mungkin gagal jika Anda memiliki pohon yang sangat dalam.
Inilah pendapat saya tentang pertanyaan ini. Ketika kita memeriksa kedalaman (jarak dari akar) dari kedua node, jika keduanya sama, maka kita dapat dengan aman bergerak ke atas dari kedua node menuju leluhur yang sama. Jika salah satu dari kedalaman lebih besar maka kita harus bergerak ke atas dari simpul yang lebih dalam sambil tetap di yang lain.
Ini kodenya:
Kompleksitas waktu dari algoritma ini adalah: O (n). Kompleksitas ruang dari algoritma ini adalah: O (1).
Mengenai perhitungan kedalaman, pertama-tama kita dapat mengingat definisi: Jika v adalah root, depth (v) = 0; Kalau tidak, kedalaman (v) = kedalaman (induk (v)) + 1. Kita dapat menghitung kedalaman sebagai berikut:
sumber
Nah, jenis ini tergantung bagaimana Binary Tree Anda terstruktur. Agaknya Anda memiliki beberapa cara untuk menemukan simpul daun yang diinginkan mengingat akar pohon - cukup menerapkannya pada kedua nilai sampai cabang yang Anda pilih berbeda.
Jika Anda tidak memiliki cara untuk menemukan daun yang diinginkan diberikan root, maka satu-satunya solusi Anda - baik dalam operasi normal dan untuk menemukan simpul umum terakhir - adalah pencarian pohon dengan kekerasan.
sumber
Ini dapat ditemukan di: - http://goursaha.freeoda.com/DataStructure/LowestCommonAncestor.html
sumber
Algoritma nenek moyang paling umum yang tidak umum dari Tarjan cukup baik (lih. Juga Wikipedia ) Ada lebih banyak tentang masalah (masalah leluhur umum terendah) di Wikipedia .
sumber
Untuk mengetahui leluhur umum dari dua simpul: -
Ini akan berfungsi untuk pohon pencarian biner.
sumber
Saya telah melakukan upaya dengan gambar ilustrasi dan kode kerja di Jawa,
http://tech.bragboy.com/2010/02/least-common-ancestor-without-using.html
sumber
Algoritma rekursif di bawah ini akan berjalan di O (log N) untuk pohon biner seimbang. Jika salah satu node yang dilewatkan ke fungsi getLCA () adalah sama dengan root maka root akan menjadi LCA dan tidak perlu melakukan recussrion.
Uji kasus. [1] Kedua node n1 & n2 berada di pohon dan berada di kedua sisi node induknya. [2] Entah simpul n1 atau n2 adalah root, LCA adalah root. [3] Hanya n1 atau n2 yang ada di pohon, LCA akan menjadi simpul akar subtree kiri dari root pohon, atau LCA akan menjadi simpul akar dari subtree kanan dari root pohon.
[4] Baik n1 atau n2 tidak ada di pohon, tidak ada LCA. [5] Baik n1 dan n2 berada dalam garis lurus di samping satu sama lain, LCA akan berupa n1 atau n2 yang pernah ditutup ke akar pohon.
sumber
Hanya berjalan turun dari seluruh pohon
root
selama keduanya diberi node, katakanp
danq
, untuk mana Leluhur harus ditemukan, berada di sub-pohon yang sama (artinya nilainya lebih kecil atau keduanya lebih besar dari pada root).Ini berjalan langsung dari root ke Least Common Ancestor, tidak melihat sisa pohon, jadi cukup cepat. Beberapa cara untuk melakukannya.
jika terjadi overflow, saya akan melakukan (root.val - (long) p.val) * (root.val - (long) q.val)
sumber
sumber
Pertimbangkan pohon ini
Jika kita melakukan postorder dan preorder traversal dan menemukan pendahulu dan penerus umum yang terjadi pertama kali, kita mendapatkan leluhur yang sama.
postorder => 0,2,1,5,4,6,3,8,10,11,9,14,15,13,12,7 preorder => 7,3,1,0,2,6,4 , 5,12,9,8,11,10,13,15,14
Nenek moyang yang sama dari 8,11
dalam postorder kita memiliki => 9,14,15,13,12,7 setelah 8 & 11 di preorder kita memiliki => 7,3,1,0,2,6,4,5,12,9 sebelum 8 & 11
9 adalah angka umum pertama yang muncul setelah 8 & 11 di postorder dan sebelum 8 & 11 di preorder, maka 9 adalah jawabannya
Nenek moyang yang sama dari 5,10
11,9,14,15,13,12,7 pada postorder 7,3,1,0,2,6,4 pada preorder
7 adalah angka pertama yang muncul setelah 5,10 pada postorder dan sebelum 5,10 pada preorder, maka 7 adalah jawabannya
sumber
Jika pohon penuh biner dengan anak-anak dari simpul x sebagai 2 * x dan 2 * x + 1 daripada ada cara yang lebih cepat untuk melakukannya
bagaimana cara kerjanya
Ini berfungsi karena pada dasarnya membagi angka yang lebih besar dengan dua secara rekursif sampai kedua angka sama. Angka itu adalah leluhur bersama. Membagi secara efektif adalah operasi shift yang tepat. Jadi kita perlu menemukan awalan umum dari dua angka untuk menemukan leluhur terdekat
sumber
Dalam scala, Anda dapat:
sumber
sumber
Inilah cara C ++ untuk melakukannya. Telah mencoba untuk menjaga algoritma semudah mungkin untuk dipahami:
Bagaimana cara menggunakannya:
sumber
Cara termudah untuk menemukan Leluhur Biasa Terendah menggunakan algoritma berikut:
sumber
Saya menemukan solusinya
Bergantung pada 3 traversal, Anda dapat memutuskan siapa LCA. Dari LCA, cari jarak kedua node. Tambahkan dua jarak ini, yang merupakan jawabannya.
sumber
Inilah yang saya pikirkan,
Kompleksitas: langkah 1: O (n), langkah 2 = ~ O (n), total = ~ O (n).
sumber
Berikut adalah dua pendekatan dalam c # (.net) (keduanya dibahas di atas) untuk referensi:
Versi rekursif menemukan LCA di pohon biner (O (N) - karena paling banyak setiap node dikunjungi) (poin utama dari solusi adalah LCA adalah (a) hanya simpul di pohon biner di mana kedua elemen berada di kedua sisi subtrees (kiri). dan kanan) adalah LCA. (b) Dan juga tidak masalah simpul mana yang hadir di kedua sisi - awalnya saya mencoba untuk menjaga info itu, dan jelas fungsi rekursif menjadi sangat membingungkan, setelah saya sadari, itu menjadi sangat elegan.
Pencarian kedua node (O (N)), dan melacak jalur (menggunakan ruang ekstra - jadi, # 1 mungkin lebih unggul bahkan berpikir ruang mungkin diabaikan jika pohon biner seimbang dengan baik sehingga konsumsi memori tambahan akan tepat di O (log (N)).
sehingga jalur dibandingkan (pada dasarnya mirip dengan jawaban yang diterima - tetapi jalur dihitung dengan mengasumsikan simpul penunjuk tidak ada dalam simpul pohon biner)
Hanya untuk penyelesaian ( tidak terkait dengan pertanyaan ), LCA di BST (O (log (N))
Tes
Rekursif:
di mana versi rekursif pribadi di atas dipanggil dengan metode publik berikut:
Solusi dengan melacak jalur kedua node:
di mana FindNodeAndPath didefinisikan sebagai
BST (LCA) - tidak terkait (hanya untuk penyelesaian untuk referensi)
Tes Unit
sumber
Jika seseorang tertarik pada kode semu (untuk pekerjaan rumah di universitas) di sini adalah salah satunya.
sumber
Meskipun ini sudah dijawab, ini adalah pendekatan saya untuk masalah ini menggunakan bahasa pemrograman C. Meskipun kode menunjukkan pohon pencarian biner (sejauh memasukkan () yang bersangkutan), tetapi algoritma ini bekerja untuk pohon biner juga. Idenya adalah untuk membahas semua node yang terletak dari node A ke node B di inorder traversal, mencari indeks untuk ini di post order traversal. Node dengan indeks maksimum dalam post order traversal adalah leluhur umum terendah.
Ini adalah kode C yang berfungsi untuk mengimplementasikan fungsi untuk menemukan leluhur umum terendah dalam pohon biner. Saya menyediakan semua fungsi utilitas dll juga, tetapi lompat ke CommonAncestor () untuk pemahaman cepat.
sumber
Mungkin ada satu pendekatan lagi. Namun itu tidak seefisien yang sudah disarankan dalam jawaban.
Buat vektor jalur untuk simpul n1.
Buat vektor jalur kedua untuk simpul n2.
Path vector menyiratkan set node dari yang akan dilintasi untuk mencapai node yang dimaksud.
Bandingkan kedua vektor jalur. Indeks di mana mereka tidak cocok, mengembalikan simpul pada indeks itu - 1. Ini akan memberikan LCA.
Kontra untuk pendekatan ini:
Perlu melintasi pohon dua kali untuk menghitung vektor jalur. Perlu ruang O (h) tambahan untuk menyimpan vektor jalur.
Namun ini mudah diimplementasikan dan dipahami juga.
Kode untuk menghitung vektor jalur:
sumber
Coba seperti ini
sumber
Cara kasar:
Masalah dengan metode di atas adalah bahwa kita akan melakukan "menemukan" beberapa kali, yaitu ada kemungkinan setiap node akan dilintasi beberapa kali. Kami dapat mengatasi masalah ini jika kami dapat merekam informasi sehingga tidak memprosesnya lagi (pikirkan pemrograman dinamis).
Jadi daripada melakukan menemukan setiap node, kami mencatat apa yang sudah ditemukan.
Cara yang lebih baik:
Kode:
sumber
Kode untuk Pencarian Pertama Luasnya untuk memastikan kedua node berada di pohon. Hanya kemudian bergerak maju dengan pencarian LCA. Berikan komentar jika Anda memiliki saran untuk ditingkatkan. Saya pikir kita mungkin dapat menandai mereka mengunjungi dan memulai kembali pencarian pada titik tertentu di mana kita tinggalkan untuk meningkatkan untuk simpul kedua (jika tidak ditemukan VISITED)
sumber
Anda benar bahwa tanpa simpul orangtua, solusi dengan traversal akan memberi Anda O (n) kompleksitas waktu.
Pendekatan traversal Misalkan Anda menemukan LCA untuk node A dan B, pendekatan yang paling mudah adalah pertama-tama mendapatkan jalur dari root ke A dan kemudian mendapatkan jalur dari root ke B. Setelah Anda memiliki dua jalur ini, Anda dapat dengan mudah beralih di atasnya dan temukan simpul umum terakhir, yang merupakan leluhur bersama terendah dari A dan B.
Solusi rekursif Pendekatan lain adalah menggunakan rekursi. Pertama, kita bisa mendapatkan LCA dari pohon kiri dan pohon kanan (jika ada). Jika salah satu dari A atau B adalah simpul root, maka root adalah LCA dan kami hanya mengembalikan root, yang merupakan titik akhir rekursi. Saat kita terus membagi pohon menjadi sub-pohon, akhirnya, kita akan menekan A dan B.
Untuk menggabungkan solusi sub-masalah, jika LCA (pohon kiri) mengembalikan sebuah simpul, kita tahu bahwa A dan B berada di pohon kiri dan simpul yang dikembalikan adalah hasil akhir. Jika LCA (kiri) dan LCA (kanan) mengembalikan node yang tidak kosong, itu berarti A dan B masing-masing berada di pohon kiri dan kanan. Dalam hal ini, simpul akar adalah simpul umum terendah.
Periksa Leluhur Biasa Terendah untuk analisis dan solusi terperinci.
sumber
Beberapa solusi di sini mengasumsikan bahwa ada referensi ke simpul root, beberapa mengasumsikan bahwa tree adalah BST. Berbagi solusi saya menggunakan hashmap, tanpa referensi ke
root
simpul dan pohon bisa BST atau non-BST:sumber
Solusi 1: Rekursif - Lebih cepat
Solusi 2: Iteratif - Menggunakan pointer orangtua - Lebih lambat
sumber