Pertimbangkan pohon biner yang tidak berlabel dan berakar. Kami dapat kompres pohon seperti: setiap kali ada pointer ke sub pohon dan dengan (menafsirkan kesetaraan struktural), kami menyimpan (wlog) dan mengganti semua pointer ke dengan pointer ke . Lihat jawaban uli sebagai contoh. T = T ′ = T T ′ T
Berikan algoritma yang mengambil pohon dalam arti di atas sebagai input dan menghitung jumlah (minimal) node yang tersisa setelah kompresi. Algoritma harus dijalankan dalam waktu (dalam model biaya seragam) dengan jumlah node dalam input.n
Ini merupakan pertanyaan ujian dan saya belum dapat menemukan solusi yang bagus, saya juga belum melihatnya.
algorithms
data-structures
trees
binary-trees
Raphael
sumber
sumber
Jawaban:
Ya, Anda dapat melakukan kompresi ini dalam waktu , tetapi itu tidak mudah :) Pertama-tama kita membuat beberapa pengamatan dan kemudian menyajikan algoritma. Kami menganggap pohon tersebut awalnya tidak dikompresi - ini tidak benar-benar diperlukan tetapi membuat analisis lebih mudah.O(nlogn)
Pertama, kami mencirikan 'kesetaraan struktural' secara induktif. Biarkan dan menjadi dua (sub) pohon. Jika dan keduanya pohon nol (tidak memiliki simpul sama sekali), mereka secara struktural setara. Jika dan keduanya bukan pohon nol, maka mereka secara struktural setara jika anak-anak kiri mereka setara secara struktural dan anak-anak kanan mereka setara secara struktural. 'Kesetaraan struktural' adalah titik tetap minimal atas definisi-definisi ini.T ′ T T ′ T T ′T T′ T T′ T T′
Sebagai contoh, setiap dua simpul daun secara struktural setara, karena mereka berdua memiliki pohon nol sebagai kedua anak-anak mereka, yang secara struktural setara.
Karena agak menjengkelkan untuk mengatakan 'anak-anak kiri mereka secara struktural setara dan begitu juga anak-anak mereka', kita akan sering mengatakan 'anak-anak mereka setara secara struktural' dan bermaksud sama. Perhatikan juga kita kadang mengatakan 'titik ini' ketika kita berarti 'subtree berakar pada titik ini'.
Definisi di atas segera memberi kita petunjuk bagaimana melakukan kompresi: jika kita mengetahui kesetaraan struktural semua sub-pohon dengan kedalaman paling banyak , maka kita dapat dengan mudah menghitung kesetaraan struktural dari sub-pohon dengan kedalaman . Kita harus melakukan perhitungan ini dengan cara yang cerdas untuk menghindari waktu berjalan .d + 1 O ( n 2 )d d+1 O(n2)
Algoritme akan menetapkan pengidentifikasi untuk setiap titik selama eksekusi. Identifier adalah angka dalam set . Pengidentifikasi unik dan tidak pernah berubah: oleh karena itu kami menganggap kami menetapkan beberapa variabel (global) ke 1 pada awal algoritma, dan setiap kali kami menetapkan pengidentifikasi ke beberapa titik, kami menetapkan nilai saat ini dari variabel tersebut ke titik dan penambahan nilai variabel itu.{1,2,3,…,n}
Kami pertama-tama mengubah pohon input menjadi (paling banyak ) daftar yang berisi simpul dengan kedalaman yang sama, bersama dengan pointer ke induknya. Ini mudah dilakukan dalam waktu O ( n ) .n O(n)
Pertama-tama kita mengompres semua daun (kita dapat menemukan daun-daun ini dalam daftar dengan simpul kedalaman 0) menjadi satu simpul tunggal. Kami menetapkan titik ini sebagai pengidentifikasi. Kompresi dua simpul dilakukan dengan mengarahkan induk dari salah satu simpul untuk menunjuk ke simpul lain sebagai gantinya.
Kami membuat dua pengamatan: pertama, setiap simpul memiliki anak-anak dengan kedalaman yang sangat kecil, dan kedua, jika kami telah melakukan kompresi pada semua simpul kedalaman yang lebih kecil dari (dan telah memberi mereka pengidentifikasi), maka dua simpul kedalaman d secara struktural setara dan dapat dikompresi jika pengidentifikasi anak-anak mereka bertepatan. Pengamatan terakhir ini mengikuti dari argumen berikut: dua simpul secara struktural setara jika anak-anak mereka setara secara struktural, dan setelah kompresi ini berarti pointer mereka menunjuk ke anak yang sama, yang pada gilirannya berarti pengidentifikasi anak-anak mereka sama.d d
Kami beralih melalui semua daftar dengan node dengan kedalaman yang sama dari kedalaman kecil ke kedalaman besar. Untuk setiap level kami membuat daftar pasangan bilangan bulat, di mana setiap pasangan sesuai dengan pengidentifikasi anak-anak dari beberapa titik pada tingkat itu. Kami memiliki dua simpul pada level tersebut yang secara struktural setara jika pasangan integer yang sesuai adalah sama. Dengan menggunakan pemesanan leksikografis, kita dapat mengurutkannya dan mendapatkan set pasangan integer yang sama. Kami memampatkan set ini ke simpul tunggal seperti di atas dan memberi mereka pengidentifikasi.
Pengamatan di atas membuktikan bahwa pendekatan ini bekerja dan menghasilkan pohon terkompresi. Total waktu berjalan adalah ditambah waktu yang dibutuhkan untuk mengurutkan daftar yang kami buat. Karena jumlah total pasangan integer yang kita buat adalah n , ini memberi kita bahwa total waktu berjalan adalah O ( n log n ) , seperti yang diperlukan. Menghitung berapa banyak node yang tersisa di akhir prosedur adalah sepele (lihat saja berapa banyak pengidentifikasi yang telah kami bagikan).O(n) n O(nlogn)
sumber
degree
" mungkin seharusnyadepth
? Dan meskipun CS-pohon tumbuh ke bawah, saya menemukan “ketinggian pohon” kurang membingungkan daripada “kedalaman pohon”.Mengkompresi struktur data yang tidak bisa diubah sehingga tidak menduplikasi subterm yang sama secara struktural dikenal sebagai hash consing . Ini adalah teknik penting dalam manajemen memori dalam pemrograman fungsional. Hash consing adalah semacam memoisasi sistematis untuk struktur data.
Saya akan menganggap pohon memiliki struktur berikut (ditulis di sini dalam sintaksis Haskell):
Kita dapat menggunakan struktur data waktu logaritmik untuk
nodes
, seperti pohon pencarian biner seimbang. Di bawah ini saya akan memanggillookup nodes
operasi yang mencari kunci dalamnodes
struktur data, daninsert nodes
operasi yang menambahkan nilai di bawah kunci baru dan mengembalikan kunci itu.Sekarang kita melintasi pohon dan menambahkan node saat kita melanjutkan. Walaupun saya menulis dalam pseudocode mirip Haskell, saya akan memperlakukannya
nodes
sebagai variabel global yang bisa berubah; kami hanya akan menambahkannya, tetapi sisipan perlu di utas seluruhnya. Theadd
fungsi recurses di pohon, menambahkan sub pohon kepadanodes
peta, dan mengembalikan identifier dari akar.Jumlah
insert
panggilan, yang juga merupakan ukuran akhir darinodes
struktur data, adalah jumlah node setelah kompresi maksimum. (Tambahkan satu untuk pohon kosong jika diperlukan.)sumber
nodes
insert
danadd
harus dibuat eksplisit dan fungsi yang sebenarnya memecahkan masalah harus diberikan, imho.nodes
variabel yang bisa berubah untuk kenyamanan, tetapi Anda dapat memasangnya di seluruh. Saya tidak akan memberikan kode lengkap, ini bukan SO.Berikut adalah ide lain yang bertujuan (secara injeksi) mengkodekan struktur pohon menjadi angka, bukan hanya memberi label secara sewenang-wenang. Untuk itu, kami menggunakan bahwa factorisation utama nomor apa pun adalah unik.
Asumsi terakhir ini adalah peregangan pada mesin nyata; dalam hal ini, seseorang akan lebih suka menggunakan sesuatu yang mirip dengan fungsi pemasangan Cantor daripada eksponensial.
sumber
Karena gambar tidak diperbolehkan dalam komentar:
kiri atas: pohon input
kanan atas: subpohon yang berakar pada node 5 dan 7 juga isomorfik.
kiri bawah dan kanan: pohon yang dikompresi tidak didefinisikan secara unik.
sumber
Sunting: Saya membaca pertanyaan karena T dan T ′ adalah anak-anak dari orang tua yang sama. Saya mengambil definisi kompresi untuk menjadi rekursif juga, yang berarti Anda dapat mengompres dua subtree yang sebelumnya dikompresi. Jika itu bukan pertanyaan aktual, maka jawaban saya mungkin tidak berfungsi.
Di mana
hasSameStructure()
ada fungsi yang membandingkan dua subtree yang telah dikompresi dalam waktu linier untuk melihat apakah mereka memiliki struktur yang sama persis. Menulis fungsi rekursif waktu linier yang melintasi masing-masing dan memeriksa apakah subtree memiliki anak kiri setiap kali yang lain lakukan dll seharusnya tidak sulit.sumber