เหตุใดอัลกอริทึมของ Dijkstra จึงใช้ปุ่มลดขนาด


95

อัลกอริทึมของ Dijkstra ได้รับการสอนให้ฉันมีดังนี้

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)

แต่ฉันได้อ่านเกี่ยวกับอัลกอริทึมและมีหลายเวอร์ชันที่ฉันเห็นว่าใช้ปุ่มลดขนาดแทนที่จะเป็นส่วนแทรก

เหตุใดจึงเป็นเช่นนี้และทั้งสองแนวทางแตกต่างกันอย่างไร


14
Downvoter- คุณช่วยอธิบายได้ไหมว่าคำถามนี้มีอะไรผิดปกติ ฉันคิดว่ามันยุติธรรมอย่างสมบูรณ์แบบและหลาย ๆ คน (รวมถึงฉัน) ได้รับการแนะนำให้รู้จักกับ Dijkstra เวอร์ชัน OP ของ OP แทนที่จะเป็นเวอร์ชันลดคีย์
templatetypedef

คำตอบ:


71

เหตุผลในการใช้คีย์ลดลงแทนที่จะใส่โหนดใหม่คือเพื่อให้จำนวนโหนดในคิวลำดับความสำคัญมีขนาดเล็กจึงทำให้จำนวนคิวลำดับความสำคัญทั้งหมดมีขนาดเล็กและค่าใช้จ่ายของยอดคงเหลือของคิวลำดับความสำคัญแต่ละรายการต่ำ

ในการใช้อัลกอริทึมของ Dijkstra ที่แทรกโหนดเข้าไปในคิวลำดับความสำคัญอีกครั้งด้วยลำดับความสำคัญใหม่ของพวกเขาโหนดหนึ่งจะถูกเพิ่มลงในคิวลำดับความสำคัญสำหรับแต่ละขอบ m ในกราฟ ซึ่งหมายความว่ามีการดำเนินการ m enqueue และการดำเนินการ m dequeue บนคิวลำดับความสำคัญโดยให้รันไทม์ทั้งหมดเป็น O (m T e + m T d ) โดยที่ T eคือเวลาที่ต้องจัดคิวในลำดับความสำคัญและ T dคือ เวลาที่ต้องใช้ในการจัดคิวจากคิวลำดับความสำคัญ

ในการใช้อัลกอริทึมของ Dijkstra ที่รองรับคีย์ลดลงคิวลำดับความสำคัญที่ถือโหนดจะเริ่มต้นด้วยโหนดในนั้นและในแต่ละขั้นตอนของอัลกอริทึมจะลบหนึ่งโหนด ซึ่งหมายความว่าจำนวนทั้งหมดของ heap dequeues คือ n แต่ละโหนดจะมีคีย์ลดลงที่เรียกว่าหนึ่งครั้งสำหรับแต่ละขอบที่นำเข้ามาดังนั้นจำนวนคีย์ลดลงทั้งหมดที่ทำได้สูงสุด m สิ่งนี้ให้รันไทม์เป็น (n T e + n T d + m T k ) โดยที่ T kคือเวลาที่ต้องใช้ในการเรียกคีย์ลดลง

แล้วสิ่งนี้มีผลอย่างไรกับรันไทม์? ขึ้นอยู่กับลำดับความสำคัญที่คุณใช้ นี่คือตารางสั้น ๆ ที่แสดงลำดับความสำคัญที่แตกต่างกันและเวลาทำงานโดยรวมของการใช้อัลกอริทึมของ Dijkstra:

Queue          |  T_e   |  T_d   |  T_k   | w/o Dec-Key |   w/Dec-Key
---------------+--------+--------+--------+-------------+---------------
Binary Heap    |O(log N)|O(log N)|O(log N)| O(M log N)  |   O(M log N)
Binomial Heap  |O(log N)|O(log N)|O(log N)| O(M log N)  |   O(M log N)
Fibonacci Heap |  O(1)  |O(log N)|  O(1)  | O(M log N)  | O(M + N log N)

อย่างที่คุณเห็นในคิวลำดับความสำคัญส่วนใหญ่ไม่มีความแตกต่างในรันไทม์แบบไม่แสดงอาการและเวอร์ชันคีย์ลดลงไม่น่าจะทำได้ดีกว่ามากนัก อย่างไรก็ตามหากคุณใช้การใช้งานFibonacci heapของลำดับความสำคัญอัลกอริทึมของ Dijkstra จะมีประสิทธิภาพมากขึ้นอย่างไม่มีอาการเมื่อใช้การลดคีย์

ในระยะสั้นการใช้ปุ่มลดขนาดบวกกับคิวลำดับความสำคัญที่ดีสามารถทำให้รันไทม์ที่ไม่แสดงอาการของ Dijkstra เกินกว่าที่จะเป็นไปได้หากคุณยังคงทำ enqueues และ dequeues

นอกเหนือจากจุดนี้แล้วอัลกอริทึมขั้นสูงบางอย่างเช่นอัลกอริทึมเส้นทางที่สั้นที่สุดของ Gabow ใช้อัลกอริทึมของ Dijkstra เป็นรูทีนย่อยและอาศัยการใช้คีย์ลดลงอย่างมาก พวกเขาใช้ความจริงที่ว่าหากคุณทราบช่วงของระยะทางที่ถูกต้องล่วงหน้าคุณสามารถสร้างลำดับความสำคัญที่มีประสิทธิภาพสูงสุดตามข้อเท็จจริงนั้น

หวังว่านี่จะช่วยได้!


1
+1: ฉันลืมที่จะอธิบายถึงฮีป การเล่นลิ้นหนึ่งครั้งเนื่องจากฮีปของเวอร์ชันแทรกมีโหนดต่อขอบเช่น O (m) เวลาในการเข้าถึงไม่ควรเป็น O (บันทึก m) โดยให้เวลาทำงานทั้งหมดเป็น O (m log m) หรือไม่? ฉันหมายความว่าในกราฟปกติ m จะไม่มากกว่า n ^ 2 ดังนั้นสิ่งนี้จึงลดลงเป็น O (m log n) แต่ในกราฟที่สองโหนดอาจรวมกันด้วยขอบหลาย ๆ ด้านที่มีน้ำหนักต่างกัน m จะไม่ถูกผูกมัด (แน่นอน เราสามารถอ้างได้ว่าเส้นทางขั้นต่ำระหว่างสองโหนดใช้ขอบน้อยที่สุดเท่านั้นและลดสิ่งนี้เป็นกราฟปกติ แต่สำหรับ nonce นั้นน่าสนใจ)
rampion

2
@ rampion- คุณมีประเด็น แต่เนื่องจากฉันคิดว่าโดยทั่วไปแล้วจะถือว่าขอบขนานลดลงก่อนที่จะเริ่มอัลกอริทึมฉันไม่คิดว่า O (log n) เทียบกับ O (log m) จะมีความสำคัญมาก โดยปกติ m จะถือว่าเป็น O (n ^ 2)
templatetypedef

28

ในปี 2550 มีเอกสารที่ศึกษาความแตกต่างของเวลาดำเนินการระหว่างการใช้เวอร์ชันลดคีย์และเวอร์ชันแทรก ดูhttp://www.cs.utexas.edu/users/shaikat/papers/TR-07-54.pdf

ข้อสรุปพื้นฐานของพวกเขาคือไม่ใช้ปุ่มลดลงสำหรับกราฟส่วนใหญ่ โดยเฉพาะอย่างยิ่งสำหรับกราฟกระจัดกระจายคีย์ไม่ลดจะเร็วกว่าเวอร์ชันคีย์ลดลงอย่างมาก ดูรายละเอียดเพิ่มเติมในกระดาษ


7
cs.sunysb.edu/~rezaul/papers/TR-07-54.pdfเป็นลิงก์ที่ใช้งานได้สำหรับเอกสารดังกล่าว
eleanora

คำเตือน: มีข้อบกพร่องในกระดาษที่เชื่อมโยง หน้า 16 ฟังก์ชั่น B.2: ควรจะเป็นif k < d[u] if k <= d[u]
Xeverous

2

มีสองวิธีในการใช้งาน Dijkstra: วิธีหนึ่งใช้ฮีปที่รองรับการลดคีย์และอีกวิธีหนึ่งคือฮีปที่ไม่รองรับ

ทั้งคู่ใช้ได้โดยทั่วไป แต่มักเป็นที่ต้องการ ต่อไปนี้ฉันจะใช้ 'm' เพื่อแสดงจำนวนขอบและ 'n' เพื่อแสดงจำนวนจุดยอดของกราฟของเรา:

หากคุณต้องการความซับซ้อนของกรณีที่เลวร้ายที่สุดเท่าที่จะเป็นไปได้คุณจะใช้ฮีป Fibonacci ที่รองรับการลดคีย์: คุณจะได้ O (m + nlogn) ที่ดี

หากคุณสนใจกรณีเฉลี่ยคุณสามารถใช้ไบนารีฮีปได้เช่นกันคุณจะได้รับบันทึก O (m + nlog (m / n)) หลักฐานอยู่ที่นี่หน้า 99/100 ถ้ากราฟมีความหนาแน่น (m >> n) ทั้งกราฟนี้และกราฟก่อนหน้าจะเป็น O (m)

หากคุณต้องการทราบว่าจะเกิดอะไรขึ้นหากคุณเรียกใช้กราฟจริงคุณสามารถตรวจสอบเอกสารนี้ได้ตามที่ Mark Meketon แนะนำในคำตอบของเขา

สิ่งที่ผลการทดลองจะแสดงก็คือฮีปที่ "เรียบง่ายกว่า" จะให้ผลลัพธ์ที่ดีที่สุดในกรณีส่วนใหญ่

ในความเป็นจริงในการนำไปใช้งานที่ใช้ปุ่มลดขนาด Dijkstra จะทำงานได้ดีกว่าเมื่อใช้ Binary heap หรือ Pairing heap มากกว่าเมื่อใช้ฮีป Fibonacci เนื่องจาก Fibonacci heaps เกี่ยวข้องกับปัจจัยคงที่ที่ใหญ่กว่าและจำนวนการดำเนินการลดคีย์ที่แท้จริงมีแนวโน้มที่จะน้อยกว่าที่กรณีที่เลวร้ายที่สุดคาดการณ์ไว้มาก

ด้วยเหตุผลที่คล้ายกันฮีปที่ไม่ต้องรองรับการดำเนินการลดคีย์จึงมีปัจจัยคงที่น้อยกว่าและทำงานได้ดีที่สุด โดยเฉพาะอย่างยิ่งถ้ากราฟเบาบาง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.