Tại sao thuật toán Dijkstra sử dụng phím giảm?


93

Thuật toán Dijkstra đã được dạy cho tôi như sau

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)

Nhưng tôi đã đọc một số liên quan đến thuật toán và nhiều phiên bản tôi thấy sử dụng phím giảm thay vì chèn.

Tại sao lại như vậy, và sự khác biệt giữa hai cách tiếp cận là gì?


14
Downvoter- Bạn có thể vui lòng giải thích điều gì sai với câu hỏi này không? Tôi nghĩ điều đó hoàn toàn công bằng, và nhiều người (bao gồm cả tôi) lần đầu tiên được giới thiệu phiên bản OP của Dijkstra hơn là phiên bản phím giảm.
templatetypedef

Câu trả lời:


68

Lý do sử dụng khóa giảm hơn là chèn lại các nút là để giữ cho số lượng nút trong hàng đợi ưu tiên nhỏ, do đó giữ cho tổng số hàng đợi ưu tiên nhỏ hơn và chi phí của mỗi số dư hàng ưu tiên thấp.

Trong việc triển khai thuật toán Dijkstra đưa các nút vào hàng đợi ưu tiên với các mức độ ưu tiên mới của chúng, một nút được thêm vào hàng đợi ưu tiên cho mỗi cạnh trong số m cạnh trong biểu đồ. Điều này có nghĩa là có m thao tác xếp hàng và m thao tác xếp hàng trên hàng đợi ưu tiên, cho tổng thời gian chạy là O (m T e + m T d ), trong đó T e là thời gian cần thiết để xếp vào hàng đợi ưu tiên và T d là thời gian cần thiết để xếp hàng từ hàng đợi ưu tiên.

Trong quá trình triển khai thuật toán Dijkstra hỗ trợ khóa giảm, hàng đợi ưu tiên giữ các nút bắt đầu với n nút trong đó và trên mỗi bước của thuật toán sẽ loại bỏ một nút. Điều này có nghĩa là tổng số thứ tự của đống là n. Mỗi nút sẽ có khóa giảm được gọi trên đó một lần cho mỗi cạnh dẫn vào nó, vì vậy tổng số khóa giảm được thực hiện tối đa là m. Điều này cho ta thời gian chạy là (n T e + n T d + m T k ), trong đó T k là thời gian cần thiết để gọi phím giảm.

Vậy điều này có ảnh hưởng gì đến thời gian chạy? Điều đó phụ thuộc vào hàng đợi ưu tiên bạn sử dụng. Dưới đây là một bảng nhanh hiển thị các hàng đợi ưu tiên khác nhau và thời gian chạy tổng thể của các triển khai thuật toán Dijkstra khác nhau:

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)

Như bạn có thể thấy, với hầu hết các loại hàng đợi ưu tiên, thực sự không có sự khác biệt trong thời gian chạy tiệm cận và phiên bản khóa giảm có khả năng làm tốt hơn. Tuy nhiên, nếu bạn sử dụng triển khai đống Fibonacci của hàng đợi ưu tiên, thì thực sự thuật toán Dijkstra sẽ tiệm cận hiệu quả hơn khi sử dụng phím giảm.

Nói tóm lại, việc sử dụng phím giảm, cộng với hàng đợi ưu tiên tốt, có thể làm giảm thời gian chạy tiệm cận của Dijkstra vượt quá những gì có thể nếu bạn tiếp tục xếp hàng và xếp hàng.

Bên cạnh điểm này, một số thuật toán nâng cao hơn, chẳng hạn như Thuật toán các đường ngắn nhất của Gabow, sử dụng thuật toán Dijkstra như một chương trình con và phụ thuộc nhiều vào việc triển khai khóa giảm. Họ sử dụng thực tế rằng nếu bạn biết trước phạm vi khoảng cách hợp lệ, bạn có thể xây dựng hàng đợi ưu tiên siêu hiệu quả dựa trên thực tế đó.

Hi vọng điêu nay co ich!


1
+1: Tôi đã quên tính toán đống. Một cách rõ ràng, vì heap của phiên bản chèn có một nút trên mỗi cạnh, tức là O (m), nên thời gian truy cập của nó không phải là O (log m), cho tổng thời gian chạy là O (m log m)? Ý tôi là, trong một đồ thị thông thường, m không lớn hơn n ^ 2, vì vậy điều này giảm xuống còn O (m log n), nhưng trong một đồ thị mà hai nút có thể được nối bởi nhiều cạnh có trọng số khác nhau, m là không bị chặn (tất nhiên , chúng ta có thể khẳng định rằng đường dẫn nhỏ nhất giữa hai nút chỉ sử dụng các cạnh tối thiểu và giảm điều này thành một đồ thị bình thường, nhưng đối với trường hợp này, điều đó thật thú vị).
rapter

2
@ raosystem- Bạn có lý, nhưng vì tôi nghĩ rằng người ta thường cho rằng các cạnh song song đã được giảm bớt trước khi kích hoạt thuật toán, tôi không nghĩ rằng O (log n) so với O (log m) sẽ quan trọng. Thông thường m được giả sử là O (n ^ 2).
templatetypedef

27

Vào năm 2007, có một bài báo nghiên cứu sự khác biệt về thời gian thực thi giữa việc sử dụng phiên bản khóa giảm và phiên bản chèn. Xem http://www.cs.utexas.edu/users/shaikat/papers/TR-07-54.pdf

Kết luận cơ bản của họ là không sử dụng phím giảm cho hầu hết các biểu đồ. Đặc biệt đối với đồ thị thưa thớt, phím không giảm nhanh hơn đáng kể so với phiên bản có phím giảm. Xem bài báo để biết thêm chi tiết.


7
cs.sunysb.edu/~rezaul/papers/TR-07-54.pdf là một liên kết hoạt động cho bài báo đó.
eleanora,

CẢNH BÁO: có lỗi trong giấy được liên kết. Trang 16, hàm B.2: if k < d[u]nên được if k <= d[u].
Xeverous

2

Có hai cách để triển khai Dijkstra: một cách sử dụng một heap hỗ trợ khóa giảm và một heap khác không hỗ trợ điều đó.

Nhìn chung, cả hai đều hợp lệ, nhưng cái sau thường được ưu tiên hơn. Trong phần sau, tôi sẽ sử dụng 'm' để biểu thị số cạnh và 'n' để biểu thị số đỉnh của đồ thị của chúng ta:

Nếu bạn muốn có độ phức tạp trong trường hợp xấu nhất có thể tốt nhất, bạn sẽ sử dụng đống Fibonacci hỗ trợ phím giảm: bạn sẽ nhận được chữ O (m + nlogn) đẹp.

Nếu bạn quan tâm đến trường hợp trung bình, bạn cũng có thể sử dụng một đống nhị phân: bạn sẽ nhận được O (m + nlog (m / n) logn). Bằng chứng là ở đây , trang 99/100. Nếu đồ thị dày đặc (m >> n), cả đồ thị này và đồ thị trước đều có xu hướng là O (m).

Nếu bạn muốn biết điều gì xảy ra nếu bạn chạy chúng trên đồ thị thực, bạn có thể kiểm tra bài báo này , như Mark Meketon đã đề xuất trong câu trả lời của mình.

Kết quả thí nghiệm sẽ cho thấy là một đống "đơn giản" hơn sẽ cho kết quả tốt nhất trong hầu hết các trường hợp.

Trên thực tế, trong số các triển khai sử dụng khóa giảm, Dijkstra hoạt động tốt hơn khi sử dụng một đống Nhị phân đơn giản hoặc một đống Ghép nối so với khi nó sử dụng một đống Fibonacci. Điều này là do đống Fibonacci liên quan đến các yếu tố không đổi lớn hơn và số lượng hoạt động khóa giảm thực tế có xu hướng nhỏ hơn nhiều so với dự đoán của trường hợp xấu nhất.

Vì những lý do tương tự, một đống không phải hỗ trợ thao tác phím giảm, thậm chí có ít yếu tố liên tục hơn và thực sự hoạt động tốt nhất. Đặc biệt nếu đồ thị thưa thớt.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.