Algoritma Dijkstra yang diajarkan kepada saya adalah sebagai berikut
while pqueue is not empty:
distance, node = pqueue.delete_min()
if node has been visited:
continue
else:
mark node as visited
if node == target:
break
for each neighbor of node:
pqueue.insert(distance + distance_to_neighbor, neighbor)
Tetapi saya telah melakukan beberapa bacaan tentang algoritme, dan banyak versi yang saya lihat menggunakan tombol pengurang daripada memasukkan.
Mengapa demikian, dan apa perbedaan antara kedua pendekatan tersebut?
Jawaban:
Alasan untuk menggunakan kunci penurunan daripada memasukkan kembali node adalah untuk menjaga jumlah node dalam antrian prioritas kecil, sehingga menjaga jumlah total antrian prioritas kecil dan biaya setiap keseimbangan antrian prioritas rendah.
Dalam implementasi algoritma Dijkstra yang memasukkan kembali node ke dalam antrian prioritas dengan prioritas barunya, satu node ditambahkan ke antrian prioritas untuk setiap m edge dalam grafik. Ini berarti bahwa ada operasi antrian dan m operasi dequeue pada antrian prioritas, memberikan total runtime dari O (m T e + m T d ), di mana T e adalah waktu yang dibutuhkan untuk masuk ke antrian prioritas dan T d adalah waktu yang diperlukan untuk membatalkan antrean prioritas.
Dalam implementasi algoritma Dijkstra yang mendukung kunci-penurunan, antrian prioritas yang menahan node dimulai dengan n node di dalamnya dan pada setiap langkah algoritma menghapus satu node. Ini berarti jumlah heap dequeue adalah n. Setiap node akan memiliki kunci penurunan yang dipanggil secara potensial sekali untuk setiap tepi yang mengarah ke dalamnya, sehingga jumlah total kunci penurunan yang dilakukan paling banyak m. Ini memberikan runtime (n T e + n T d + m T k ), di mana T k adalah waktu yang dibutuhkan untuk memanggil tombol-menurun.
Jadi, apa efeknya pada runtime? Itu tergantung pada antrian prioritas apa yang Anda gunakan. Berikut adalah tabel singkat yang menunjukkan antrian prioritas yang berbeda dan keseluruhan runtime dari implementasi algoritma Dijkstra yang berbeda:
Seperti yang Anda lihat, dengan sebagian besar jenis antrean prioritas, sebenarnya tidak ada perbedaan dalam runtime asimtotik, dan versi kunci kecil kemungkinan tidak akan bekerja lebih baik. Namun, jika Anda menggunakan implementasi tumpukan Fibonacci dari antrian prioritas, maka memang algoritme Dijkstra akan lebih efisien secara asimtotik saat menggunakan kunci penurunan.
Singkatnya, menggunakan kunci penurunan, ditambah antrean prioritas yang baik, dapat menghilangkan waktu proses asimtotik Dijkstra melebihi kemungkinan jika Anda terus melakukan antrean dan antrean.
Selain poin ini, beberapa algoritma yang lebih maju, seperti Algoritma Jalur Terpendek Gabow, menggunakan algoritma Dijkstra sebagai subrutin dan sangat bergantung pada implementasi kunci penurunan. Mereka menggunakan fakta bahwa jika Anda mengetahui kisaran jarak yang valid sebelumnya, Anda dapat membangun antrian prioritas super efisien berdasarkan fakta tersebut.
Semoga ini membantu!
sumber
Pada tahun 2007, ada makalah yang mempelajari perbedaan waktu eksekusi antara menggunakan versi kunci penurunan dan versi sisipan. Lihat http://www.cs.utexas.edu/users/shaikat/papers/TR-07-54.pdf
Kesimpulan dasar mereka adalah tidak menggunakan kunci penurunan untuk sebagian besar grafik. Khusus untuk grafik renggang, kunci non-penurunan jauh lebih cepat daripada versi kunci penurunan. Lihat kertas untuk lebih jelasnya.
sumber
if k < d[u]
seharusnyaif k <= d[u]
.Ada dua cara untuk mengimplementasikan Dijkstra: satu menggunakan heap yang mendukung kunci-penurunan dan satu lagi heap yang tidak mendukung itu.
Keduanya valid secara umum, tetapi yang terakhir biasanya lebih disukai. Berikut ini saya akan menggunakan 'm' untuk menunjukkan jumlah sisi dan 'n' untuk menunjukkan jumlah simpul dari grafik kita:
Jika Anda menginginkan kompleksitas kasus terburuk sebaik mungkin, Anda akan menggunakan tumpukan Fibonacci yang mendukung kunci penurunan: Anda akan mendapatkan O (m + nlogn) yang bagus.
Jika Anda peduli dengan kasus rata-rata, Anda dapat menggunakan tumpukan Biner juga: Anda akan mendapatkan O (m + nlog (m / n) logn). Bukti ada di sini , halaman 99/100. Jika grafiknya padat (m >> n), grafik yang satu ini dan yang sebelumnya cenderung ke O (m).
Jika Anda ingin mengetahui apa yang terjadi jika Anda menjalankannya pada grafik nyata, Anda dapat memeriksa makalah ini , seperti yang disarankan Mark Meketon dalam jawabannya.
Hasil eksperimen akan menunjukkan bahwa heap yang "lebih sederhana" akan memberikan hasil terbaik dalam banyak kasus.
Faktanya, di antara implementasi yang menggunakan kunci penurunan, Dijkstra berkinerja lebih baik saat menggunakan heap Biner sederhana atau heap Pairing daripada saat menggunakan heap Fibonacci. Ini karena tumpukan Fibonacci melibatkan faktor konstanta yang lebih besar dan jumlah aktual operasi kunci penurunan cenderung jauh lebih kecil daripada prediksi kasus terburuk.
Untuk alasan yang serupa, heap yang tidak harus mendukung operasi kunci-penurunan, memiliki faktor yang kurang konstan dan benar-benar berkinerja terbaik. Apalagi jika grafiknya jarang.
sumber