Tại sao thuật toán của Dijkstra không hoạt động cho các cạnh trọng lượng âm?


121

Ai đó có thể cho tôi biết tại sao thuật toán của Dijkstra cho đường dẫn ngắn nhất nguồn đơn giả định rằng các cạnh phải không âm.

Tôi đang nói về chỉ cạnh chứ không phải chu kỳ trọng lượng âm.


3
Một câu trả lời đúng với một ví dụ hay sẽ là: stackoverflow.com/questions/6799172/ trên
Amitk

Câu trả lời:


175

Hãy nhớ lại rằng trong thuật toán của Dijkstra, một khi một đỉnh được đánh dấu là "đóng" (và ngoài tập mở) - thuật toán tìm thấy đường đi ngắn nhất tới nó và sẽ không bao giờ phải phát triển nút này nữa - nó giả định đường dẫn được phát triển tới đây con đường là ngắn nhất.

Nhưng với trọng lượng tiêu cực - nó có thể không đúng. Ví dụ:

       A
      / \
     /   \
    /     \
   5       2
  /         \
  B--(-10)-->C

V={A,B,C} ; E = {(A,C,2), (A,B,5), (B,C,-10)}

Dijkstra từ A trước tiên sẽ phát triển C và sau đó sẽ không tìm thấy A->B->C


EDIT giải thích sâu hơn một chút:

Lưu ý rằng điều này rất quan trọng, bởi vì trong mỗi bước thư giãn, thuật toán giả định "chi phí" cho các nút "đóng" thực sự là tối thiểu, và do đó nút sẽ được chọn tiếp theo cũng là tối thiểu.

Ý tưởng của nó là: Nếu chúng ta có một đỉnh mở sao cho chi phí của nó là tối thiểu - bằng cách thêm bất kỳ số dương nào vào bất kỳ đỉnh nào - mức tối thiểu sẽ không bao giờ thay đổi.
Không có ràng buộc về số dương - giả định trên là không đúng.

Vì chúng tôi "biết" mỗi đỉnh "đóng" là tối thiểu - chúng tôi có thể thực hiện bước thư giãn một cách an toàn - mà không "nhìn lại". Nếu chúng ta cần "nhìn lại" - Bellman-Ford cung cấp giải pháp giống như đệ quy (DP) để làm như vậy.


5
Xin lỗi nhưng tôi không nhận được bất kỳ lỗi nào. Đầu tiên A->Bsẽ 5 và A->Csẽ 2. Sau đó B->Csẽ -5. Vì vậy, giá trị của Csẽ -5giống như bellman-ford. Làm thế nào là điều này không đưa ra câu trả lời đúng?
Anirban Nag 'tintinmj'

5
@tintinmj trước tiên, Dijkstra sẽ "đóng" nút Acó giá trị bằng 0. Sau đó, nó sẽ nhìn vào nút có giá trị tối thiểu, Blà 5 và Clà 2. Tối thiểu là C, vì vậy nó sẽ đóng Cvới giá trị 2 và sẽ không bao giờ nhìn lại, khi nào sau đó Bđược đóng lại, nó không thể sửa đổi giá trị của C, vì nó đã được "đóng".
amit

4
@amit Làm thế nào thuật toán của Dijkstra không tìm thấy đường dẫn A -> B -> C? Đầu tiên, nó sẽ cập nhật Ckhoảng cách thành 2, và sau đó Blà khoảng cách 5. Giả sử trong biểu đồ của bạn không có cạnh đi ra C, thì chúng ta không làm gì khi truy cập C(và khoảng cách của nó vẫn là 2). Sau đó, chúng tôi truy cập Dcác nút liền kề và nút liền kề duy nhất Ccó khoảng cách mới là -5. Lưu ý rằng trong thuật toán của Dijkstra, chúng tôi cũng theo dõi cha mẹ mà chúng tôi tiếp cận (và cập nhật) nút và thực hiện từ đó C, bạn sẽ nhận được cha mẹ B, và sau đó A, dẫn đến kết quả chính xác. Tôi đang thiếu gì?
nbro

12
@amit Vấn đề với lý luận của bạn (tôi nghĩ), và tôi đã thấy những người khác làm điều đó (kỳ lạ), là bạn nghĩ rằng thuật toán sẽ không xem xét lại các nút có khoảng cách ngắn nhất đã được xác định (và chúng ta đã hoàn thành), nhưng điều này không đúng và đó là lý do tại sao chúng ta có bước "thư giãn" ... Chúng tôi lặp lại qua tất cả các nút của biểu đồ và, đối với mỗi nút, chúng tôi lặp lại qua các nút liền kề, ngay cả khi bất kỳ nút lân cận nào có thể đã được xóa khỏi hàng đợi ưu tiên tối thiểu của chúng tôi, ví dụ.
nbro

10
@amit Kiểm tra câu trả lời này cho một câu hỏi tương tự, trong đó ví dụ thực sự có ý nghĩa: stackoverflow.com/a/6799344/3924118
nbro

37

Hãy xem xét biểu đồ được hiển thị bên dưới với nguồn là Vertex A. Trước tiên hãy thử tự chạy thuật toán của Dijkstra trên đó.

nhập mô tả hình ảnh ở đây

Khi tôi đề cập đến thuật toán của Dijkstra, tôi sẽ nói về Thuật toán của Dijkstra như được triển khai dưới đây,

Thuật toán của Dijkstra

Vì vậy, bắt đầu các giá trị ( khoảng cách từ nguồn đến đỉnh ) ban đầu được gán cho mỗi đỉnh là,

khởi tạo

Đầu tiên chúng ta trích xuất đỉnh trong Q = [A, B, C] có giá trị nhỏ nhất, tức là A, sau đó Q = [B, C] . Lưu ý A có cạnh được định hướng đến B và C, cả hai đều nằm trong Q, do đó chúng tôi cập nhật cả hai giá trị đó,

lần lặp đầu tiên

Bây giờ chúng tôi trích xuất C là (2 <5), bây giờ Q = [B] . Lưu ý rằng C được kết nối với không có gì, vì vậy line16vòng lặp không chạy.

lần lặp thứ hai

Cuối cùng chúng tôi trích xuất B, sau đó Q là Phi. Lưu ý B có cạnh được định hướng đến C nhưng C không có trong Q do đó chúng tôi lại không nhập vòng lặp for vào line16,

lần thứ 3?

Vì vậy, chúng tôi kết thúc với khoảng cách như

không thay đổi kẻ

Lưu ý làm thế nào là sai vì khoảng cách ngắn nhất từ ​​A đến C là 5 + -10 = -5, khi bạn đi từ a đến b.

Vì vậy, đối với biểu đồ này, Thuật toán của Dijkstra đã tính toán sai khoảng cách từ A đến C.

Điều này xảy ra bởi vì Dijkstra Thuật toán không cố gắng để tìm một con đường ngắn hơn để đỉnh được đã chiết xuất từ Q .

Những gì các line16vòng lặp đang làm là lấy đỉnh u và nói "hey trông giống như chúng ta có thể đi đến v từ nguồn qua u , đó là (alt hoặc thay thế) khoảng cách bất kỳ tốt hơn so với hiện tại quận [v] chúng tôi đã nhận? Nếu vậy cho phép cập nhật xa [v] "

Lưu ý rằng trong line16họ rà soát tất cả các nước láng giềng v (tức là một lợi thế cạnh đạo tồn tại từ u đến v ), của u mà là vẫn còn trong Q . Trong line14họ loại bỏ nốt truy cập từ Q. Vì vậy, nếu x là một người hàng xóm đến thăm của u , con đường nguồn để u đến xđược thậm chí không coi như là một cách thể ngắn hơn từ nguồn tới v .

Trong ví dụ của chúng tôi ở trên, C là hàng xóm đã đến thăm của B, do đó đường dẫn Từ A đến B đến Ckhông được xem xét, khiến đường dẫn ngắn nhất hiện tại Từ A đến Ckhông thay đổi.

Điều này thực sự hữu ích nếu các trọng số cạnh đều là các số dương , bởi vì sau đó chúng ta sẽ không lãng phí thời gian để xem xét các đường dẫn không thể ngắn hơn.

Vì vậy, tôi nói rằng khi chạy thuật toán này nếu x được trích xuất từ ​​Q trước y , thì không thể tìm thấy đường dẫn - không thểngắn hơn. Hãy để tôi giải thích điều này với một ví dụ,

y vừa được trích xuất và x đã được trích xuất trước chính nó, sau đó dist [y]> dist [x] bởi vì nếu không thì y sẽ được trích xuất trước x . ( line 13khoảng cách tối thiểu trước)

Và như chúng ta đã giả sử rằng các trọng số cạnh là dương, tức là chiều dài (x, y)> 0 . Vì vậy, khoảng cách thay thế (alt) qua y luôn chắc chắn là lớn hơn, tức là dist [y] + length (x, y)> dist [x] . Vì vậy, giá trị của dist [x] sẽ không được cập nhật ngay cả khi y được coi là đường dẫn đến x , do đó chúng tôi kết luận rằng chỉ nên xem xét hàng xóm của y vẫn còn trong Q (ghi chú trong line16)

Nhưng điều này phụ thuộc vào giả định của chúng tôi về độ dài cạnh dương, nếu độ dài (u, v) <0 thì tùy thuộc vào mức độ âm của cạnh đó, chúng tôi có thể thay thế dist [x] sau khi so sánh line18.

Vì vậy, bất kỳ phép tính dist [x] nào chúng ta thực hiện sẽ không chính xác nếu x bị xóa trước khi tất cả các đỉnh v - sao cho x là hàng xóm của v có cạnh âm kết nối chúng - bị xóa.

Bởi vì mỗi đỉnh v đó là đỉnh cuối cùng thứ hai trên đường dẫn "tốt hơn" tiềm năng từ nguồn đến x , được loại bỏ bởi thuật toán của Dijkstra.

Vì vậy, trong ví dụ tôi đã đưa ra ở trên, lỗi là do C đã bị xóa trước khi B bị xóa. Trong khi đó C là hàng xóm của B với lợi thế tiêu cực!

Chỉ cần làm rõ, B và C là hàng xóm của A. B có một người hàng xóm C và C không có hàng xóm. chiều dài (a, b) là chiều dài cạnh giữa các đỉnh a và b.


2
Giống như bạn đã nói, cách tốt hơn để giải quyết điều này là sử dụng phương pháp heapq.h Ứng dụng lại sau mỗi so sánh. Chúng tôi đẩy lùi khoảng cách cập nhật vào hàng đợi. Trong điều kiện này, Dijkstra có thể hoạt động trên các trọng số âm. Tôi đã thử, và kết quả được đưa ra là 0,5, -5
nosense

1
"nguồn đường dẫn đến x đến u thậm chí không được xem xét"; bạn có nghĩa là nguồn để u đến x?
slmatrix

1
@slmatrix cảm ơn vì đã nắm bắt được điều đó, vâng, ý tôi là đường dẫn từ nguồn tới u đến x, vì x là hàng xóm của u.
Aditya P

23

Thuật toán của Dijkstra giả định các đường dẫn chỉ có thể trở nên 'nặng hơn', do đó, nếu bạn có đường dẫn từ A đến B có trọng số 3 và đường dẫn từ A đến C có trọng số 3, không có cách nào bạn có thể thêm cạnh và đi từ A đến B đến C với trọng lượng dưới 3.

Giả định này làm cho thuật toán nhanh hơn các thuật toán phải tính đến trọng số âm.


8

Tính chính xác của thuật toán Dijkstra:

Chúng ta có 2 bộ đỉnh ở bất kỳ bước nào của thuật toán. Tập A bao gồm các đỉnh mà chúng ta đã tính các đường đi ngắn nhất. Tập B bao gồm các đỉnh còn lại.

Giả thuyết quy nạp : Ở mỗi bước chúng ta sẽ cho rằng tất cả các lần lặp trước là đúng.

Bước quy nạp : Khi chúng ta thêm một đỉnh V vào tập A và đặt khoảng cách là dist [V], chúng ta phải chứng minh rằng khoảng cách này là tối ưu. Nếu điều này không tối ưu thì phải có một số đường dẫn khác đến đỉnh V có độ dài ngắn hơn.

Giả sử điều này một số đường dẫn khác đi qua một số đỉnh X.

Bây giờ, vì dist [V] <= dist [X], do đó, bất kỳ đường dẫn nào khác đến V sẽ có độ dài ít nhất dist [V], trừ khi biểu đồ có độ dài cạnh âm.

Do đó, để thuật toán của dijkstra hoạt động, các trọng số cạnh phải không âm.


6

Hãy thử thuật toán của Dijkstra trên biểu đồ sau, giả sử Alà nút nguồn, để xem điều gì đang xảy ra:

Đồ thị


6
Xin lỗi nhưng tôi không nhận được bất kỳ lỗi nào. Đầu tiên A->Bsẽ 1A->Csẽ 100. Rồi B->Dsẽ 2. Rồi C->Dsẽ -4900. Vì vậy, giá trị của Dsẽ -4900giống như bellman-ford. Làm thế nào là điều này không đưa ra câu trả lời đúng?
Anirban Nag 'tintinmj'

9
@tintinmj Nếu bạn có lợi thế từ D, nó sẽ được truy cập trước khi khoảng cách của D bị giảm và do đó không được cập nhật sau đó. Điều này sau đó sẽ dẫn đến một lỗi chắc chắn. Nếu bạn coi D ​​'2 là khoảng cách cuối cùng đã có sau khi quét các cạnh đi, ngay cả biểu đồ này cũng dẫn đến lỗi.
Christian Schnorr

@ tb- Xin lỗi vì đã hỏi sau một thời gian dài như vậy, nhưng tôi có đang đi đúng hướng không? Đầu tiên A->Bsẽ 1A->Csẽ 100. Sau đó Bsẽ được tìm hiểu và bộ B->Dtới 2. Sau đó D được khám phá vì hiện tại nó có đường dẫn ngắn nhất trở về nguồn? Tôi có đúng không khi nói rằng nếu B->Dđược 100, Csẽ được khám phá trước? Tôi hiểu tất cả các ví dụ khác mọi người đưa ra ngoại trừ của bạn.
Pejman Poh

@PejmanPoh theo cách hiểu của tôi, nếu B-> D là 100, vì A-> C cao hơn trong HeapStr struct sẽ được sử dụng, trích xuất min sẽ trả về A-> C trước, có nghĩa là đường đi ngắn nhất tiếp theo sẽ là đường dẫn đến C, sau đó, đường dẫn từ C-> D có trọng lượng -5000 sẽ là lựa chọn rõ ràng, dẫn đến kết luận rằng con đường ngắn nhất sẽ là từ A-> C-> D và tôi khá chắc chắn điều này sẽ là hành vi bình thường. Vì vậy, đôi khi khi chúng ta có chu kỳ âm, chúng ta vẫn có thể nhận được giá trị phù hợp cho con đường ngắn nhất, nhưng chắc chắn không phải lúc nào cũng vậy, đây là một ví dụ mà chúng ta sẽ không ..
T.Dimitrov

1

Hãy nhớ lại rằng trong thuật toán của Dijkstra, một khi một đỉnh được đánh dấu là "đóng" (và ngoài tập mở) - nó giả định rằng bất kỳ nút nào có nguồn gốc từ nó sẽ dẫn đến khoảng cách lớn hơn , vì vậy thuật toán tìm thấy đường đi ngắn nhất tới nó, và sẽ không bao giờ phải phát triển nút này một lần nữa, nhưng điều này không đúng trong trường hợp trọng số âm.


0

Các câu trả lời khác cho đến nay cho thấy khá rõ lý do tại sao thuật toán của Dijkstra không thể xử lý các trọng số âm trên các đường dẫn.

Nhưng bản thân câu hỏi có thể dựa trên sự hiểu biết sai về trọng lượng của các con đường. Nếu các trọng số âm trên các đường dẫn sẽ được cho phép trong các thuật toán tìm đường nói chung, thì bạn sẽ có các vòng lặp vĩnh viễn không dừng lại.

Xem xét điều này:

A  <- 5 ->  B  <- (-1) ->  C <- 5 -> D

Con đường tối ưu giữa A và D là gì?

Bất kỳ thuật toán tìm đường nào cũng sẽ phải liên tục lặp giữa B và C vì làm như vậy sẽ làm giảm trọng số của tổng đường dẫn. Vì vậy, việc cho phép các trọng số âm cho một kết nối sẽ hiển thị bất kỳ thuật toán pathfindig nào, có thể ngoại trừ nếu bạn giới hạn mỗi kết nối chỉ được sử dụng một lần.


0

Bạn có thể sử dụng thuật toán của dijkstra với các cạnh âm không bao gồm chu kỳ âm, nhưng bạn phải cho phép một đỉnh có thể được truy cập nhiều lần và phiên bản đó sẽ mất độ phức tạp thời gian nhanh.

Trong trường hợp đó, thực tế tôi đã thấy tốt hơn là sử dụng thuật toán SPFA có hàng đợi bình thường và có thể xử lý các cạnh âm.

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.