Trọng số âm sử dụng Thuật toán Dijkstra


113

Tôi đang cố gắng hiểu tại sao thuật toán Dijkstra sẽ không hoạt động với trọng số âm. Đọc một ví dụ về Đường dẫn ngắn nhất , tôi đang cố gắng tìm ra tình huống sau:

    2
A-------B
 \     /
3 \   / -2
   \ /
    C

Từ trang web:

Giả sử các cạnh đều hướng từ trái sang phải, Nếu chúng ta bắt đầu với A, thuật toán Dijkstra sẽ chọn cạnh (A, x) nhỏ nhất d (A, A) + chiều dài (cạnh), cụ thể là (A, B). Sau đó, nó đặt d (A, B) = 2 và chọn một cạnh khác (y, C) nhỏ nhất d (A, y) + d (y, C); sự lựa chọn duy nhất là (A, C) và nó đặt d (A, C) = 3. Nhưng nó không bao giờ tìm thấy con đường ngắn nhất từ ​​A đến B, qua C, với tổng độ dài là 1.

Tôi không hiểu tại sao khi sử dụng cách triển khai Dijkstra sau đây, d [B] sẽ không được cập nhật lên 1(Khi thuật toán đạt đến đỉnh C, nó sẽ chạy thư giãn trên B, thấy rằng d [B] bằng 2và do đó cập nhật giá trị của nó thành 1).

Dijkstra(G, w, s)  {
   Initialize-Single-Source(G, s)
   S ← Ø
   Q ← V[G]//priority queue by d[v]
   while Q ≠ Ø do
      u ← Extract-Min(Q)
      S ← S U {u}
      for each vertex v in Adj[u] do
         Relax(u, v)
}

Initialize-Single-Source(G, s) {
   for each vertex v  V(G)
      d[v] ← ∞
      π[v] ← NIL
   d[s] ← 0
}

Relax(u, v) {
   //update only if we found a strictly shortest path
   if d[v] > d[u] + w(u,v) 
      d[v] ← d[u] + w(u,v)
      π[v] ← u
      Update(Q, v)
}

Cảm ơn,

Meir


Tìm đường nói chung với trọng số cạnh âm là vô cùng khó khăn. Bất kể bạn tìm thấy tuyến đường nào, luôn có khả năng có một tuyến đường dài tùy ý với trọng số cạnh âm lớn tùy ý ở đâu đó dọc theo nó. Tôi sẽ không ngạc nhiên nếu nó hoàn thành NP.
Nick Johnson,

4
Đối với bất kỳ ai khác có nghi ngờ này, bạn có thể tìm thấy đường đi ngắn nhất trong biểu đồ GIVEN rằng nó không có chu kỳ trọng số âm. Thuật toán trên sẽ hoạt động nếu hàm Relax trả về giá trị "true" khi relax thực sự thành công, trong trường hợp đó, đỉnh liền kề "v" sẽ được xếp vào hàng đợi ưu tiên nếu chưa có hoặc được cập nhật nếu đã có. Điều này có nghĩa là các nút đã truy cập một lần nữa có thể được thêm vào hàng đợi ưu tiên khi chúng tiếp tục được thư giãn.
goelakash

Câu trả lời:


202

Thuật toán bạn đã đề xuất thực sự sẽ tìm ra đường đi ngắn nhất trong đồ thị này, nhưng không phải tất cả các đồ thị nói chung. Ví dụ, hãy xem xét biểu đồ này:

Hình đồ thị

Giả sử các cạnh được hướng từ trái sang phải như trong ví dụ của bạn,

Thuật toán của bạn sẽ hoạt động như sau:

  1. Đầu tiên, bạn đặt d(A)thành zerovà các khoảng cách khác thành infinity.
  2. Sau đó, bạn mở rộng nút A, cài đặt d(B)thành 1, d(C)đến zerod(D)đến 99.
  3. Tiếp theo, bạn mở rộng ra C, không có thay đổi thực.
  4. Sau đó, bạn mở rộng ra B, không có tác dụng.
  5. Cuối cùng, bạn mở rộng D, thay đổi d(B)thành -201.

Lưu ý rằng ở phần cuối của điều này, mặc dù điều đó d(C)vẫn còn 0, mặc dù đường đi ngắn nhất đến Ccó độ dài -200. Do đó, thuật toán của bạn không thể tính toán chính xác khoảng cách trong một số trường hợp. Hơn nữa, ngay cả khi bạn đã lưu lại con trỏ nói như thế nào để có được từ mỗi nút đến nút khởi động A, bạn muốn kết thúc lấy con đường trở lại sai từ Cđể A.


35
Để thêm vào câu trả lời tuyệt vời của bạn: Dijkstra là một thuật toán tham lam là lý do cho sự lựa chọn thiển cận của nó.
blubb

4
Tôi muốn chỉ ra rằng, về mặt kỹ thuật, tất cả các đường đi trong biểu đồ này đều có giá trị âm vô cực do chu kỳ âm A, D, B, A.
Nate

2
@ Nate- Để làm rõ, tất cả các cạnh trong biểu đồ được hướng từ trái sang phải. Thật khó để tạo ra các mũi tên trong nghệ thuật ASCII chất lượng cao của tôi. :-)
templatetypedef

2
Đối với những người chưa từng nhìn thấy biểu đồ có cạnh âm trước đây, tôi thấy cách giải thích hữu ích của biểu đồ này là mạng lưới các con đường thu phí, nơi trọng số của cạnh cho biết mức phí bạn phải trả. Đường -300 là một con đường thu phí ngược điên cuồng, thay vào đó họ cho bạn $ 300.
D Coetzee

3
@ SchwitJanwityanujit- Đây là cách hoạt động của thuật toán Dijkstra. Thuật toán không khám phá các đường dẫn mà thay vào đó hoạt động bằng cách xử lý các nút . Mỗi nút được xử lý chính xác một lần, vì vậy ngay sau khi chúng tôi xử lý nút B và nhận được rằng khoảng cách của nó là 1, chúng tôi sẽ không bao giờ truy cập lại nút B hoặc cố gắng cập nhật khoảng cách của nó.
templatetypedef

25

Lưu ý rằng Dijkstra hoạt động ngay cả đối với các trọng số âm, nếu Đồ thị không có chu kỳ âm, tức là các chu kỳ có trọng số tổng hợp nhỏ hơn 0.

Tất nhiên người ta có thể hỏi, tại sao trong ví dụ được thực hiện bởi templatetypedef Dijkstra lại thất bại ngay cả khi không có chu kỳ âm, thậm chí không phải chu kỳ. Đó là bởi vì anh ta đang sử dụng một tiêu chí dừng khác, giữ thuật toán ngay khi đạt đến nút đích (hoặc tất cả các nút đã được giải quyết một lần, anh ta không chỉ định chính xác điều đó). Trong biểu đồ không có trọng số âm, điều này hoạt động tốt.

Nếu một người đang sử dụng tiêu chí dừng thay thế, tiêu chí dừng thuật toán khi hàng đợi ưu tiên (heap) chạy trống (tiêu chí dừng này cũng được sử dụng trong câu hỏi), thì dijkstra sẽ tìm khoảng cách chính xác ngay cả đối với đồ thị có trọng số âm nhưng không có chu kỳ âm.

Tuy nhiên, trong trường hợp này, giới hạn thời gian tiệm cận của dijkstra đối với đồ thị không có chu kỳ âm bị mất. Điều này là do một nút đã giải quyết trước đó có thể được đưa lại vào heap khi tìm thấy khoảng cách tốt hơn do trọng số âm. Thuộc tính này được gọi là hiệu chỉnh nhãn.


2. Không rõ tại sao bạn nghĩ rằng thời gian sẽ khiến tôi "giống Bellman-Ford" hơn chứ không phải theo cấp số nhân (tệ hơn Bellman-Ford). Bạn có một thuật toán cụ thể và một bằng chứng trong tâm trí không?
Gassa

3
Điều 1.: vì bạn có thể sử dụng chính xác việc triển khai dijkstra với tiêu chí dừng đã đề cập, dừng khi hàng đợi chạy rỗng (xem mã giả trong câu hỏi ban đầu), nó vẫn là thuật toán dijkstras cho các đường đi ngắn nhất, mặc dù nó hoạt động khác giải quyết các nút nhiều lần (nhãn hiệu chỉnh).
infty10000101

1
Tới 2: Đó chỉ là phỏng đoán nên tôi sẽ xóa nó. Tôi nghĩ rằng bạn đúng với thời gian theo cấp số nhân, vì có rất nhiều con đường theo cấp số nhân, phải được khám phá.
infty10000101

11

bạn đã không sử dụng S ở bất kỳ đâu trong thuật toán của mình (ngoài việc sửa đổi nó). ý tưởng về dijkstra là một đỉnh nằm trên S, nó sẽ không được sửa đổi nữa. trong trường hợp này, khi B ở bên trong S, bạn sẽ không đến được nó nữa qua C.

thực tế này đảm bảo độ phức tạp của O (E + VlogV) [nếu không, bạn sẽ lặp lại các cạnh nhiều hơn một lần và các đỉnh nhiều hơn một lần]

nói cách khác, thuật toán bạn đã đăng, có thể không nằm trong O (E + VlogV), như thuật toán của dijkstra đã hứa.


Ngoài ra, không có cần phải sửa đổi các đỉnh mà không cần cạnh trọng lượng tiêu cực, mà hoàn toàn phá vỡ giả định rằng chi phí con đường duy nhất có thể tăng lên theo mép lặp đi lặp lại
prusswan

giả định này chính xác là thứ cho phép chúng ta sử dụng S, và 'biết' một khi đỉnh nằm trong S, nó sẽ không bao giờ được sửa đổi nữa.
amit

Tuyên bố cuối cùng của bạn là sai. Thuật toán đã đăng có độ phức tạp thời gian O (E + VlogV) khi nó hoạt động trên đồ thị không có cạnh âm. Không cần phải kiểm tra xem chúng ta đã truy cập vào một nút hay chưa, vì thực tế là nó đã được truy cập đảm bảo quy trình thư giãn sẽ không thêm nó một lần nữa trong hàng đợi.
Pixar

7

Vì Dijkstra là một cách tiếp cận Tham lam, một khi một đỉnh được đánh dấu là đã truy cập cho vòng lặp này, nó sẽ không bao giờ được đánh giá lại ngay cả khi có một con đường khác với chi phí thấp hơn để đạt được nó sau này. Và vấn đề như vậy chỉ có thể xảy ra khi các cạnh âm tồn tại trong biểu đồ.


Một thuật toán tham lam , như tên cho thấy, luôn đưa ra lựa chọn có vẻ là tốt nhất tại thời điểm đó. Giả sử rằng bạn có một hàm mục tiêu cần được tối ưu hóa (tối đa hóa hoặc thu nhỏ) tại một điểm nhất định. Thuật toán Tham lam đưa ra các lựa chọn tham lam ở mỗi bước để đảm bảo rằng hàm mục tiêu được tối ưu hóa. Thuật toán Tham lam chỉ có một lần duy nhất để tính toán giải pháp tối ưu để nó không bao giờ quay trở lại và đảo ngược quyết định.


4

TL; DR: Câu trả lời phụ thuộc vào cách triển khai của bạn. Đối với mã giả mà bạn đã đăng, nó hoạt động với trọng số âm.


Các biến thể của thuật toán Dijkstra

Điều quan trọng là có 3 loại triển khai thuật toán Dijkstra , nhưng tất cả các câu trả lời trong câu hỏi này đều bỏ qua sự khác biệt giữa các biến thể này.

  1. Sử dụng -loop lồng nhaufor để thư giãn các đỉnh. Đây là cách dễ nhất để triển khai thuật toán Dijkstra. Độ phức tạp thời gian là O (V ^ 2).
  2. Triển khai dựa trên hàng đợi / đống ưu tiên + KHÔNG cho phép truy cập lại, trong đó việc vào lại có nghĩa là một đỉnh thoải mái có thể được đẩy vào hàng đợi ưu tiên một lần nữa để được nới lỏng trở lại sau .
  3. Triển khai dựa trên hàng đợi / đống ưu tiên + cho phép truy cập lại.

Phiên bản 1 & 2 sẽ không thành công trên đồ thị có trọng số âm (nếu bạn nhận được câu trả lời chính xác trong những trường hợp như vậy, đó chỉ là sự trùng hợp), nhưng phiên bản 3 vẫn hoạt động .

Mã giả được đăng theo vấn đề gốc là phiên bản 3 ở trên, vì vậy nó hoạt động với trọng số âm.

Đây là một tài liệu tham khảo tốt từ Algorithm (phiên bản thứ 4) , cho biết (và chứa cách triển khai java của phiên bản 2 & 3 mà tôi đã đề cập ở trên):

Q. Thuật toán Dijkstra có hoạt động với trọng số âm không?

A. Có và không. Có hai thuật toán đường đi ngắn nhất được gọi là thuật toán Dijkstra, tùy thuộc vào việc một đỉnh có thể được xếp vào hàng đợi ưu tiên nhiều hơn một lần hay không. Khi trọng số không âm, hai phiên bản trùng nhau (vì không đỉnh nào sẽ được xếp vào hàng nhiều hơn một lần). Phiên bản được triển khai trong DijkstraSP.java (cho phép một đỉnh được xếp hàng nhiều lần) là đúng khi có trọng số cạnh âm (nhưng không có chu kỳ âm) nhưng thời gian chạy của nó là hàm mũ trong trường hợp xấu nhất. (Chúng tôi lưu ý rằng DijkstraSP.java ném một ngoại lệ nếu đồ thị có trọng số cạnh có cạnh có trọng số âm, để lập trình viên không bị ngạc nhiên bởi hành vi theo cấp số nhân này.) Nếu chúng tôi sửa đổi DijkstraSP.java để một đỉnh không thể nằm trong hàng ngũ nhiều lần (ví dụ: sử dụng mảng [] được đánh dấu để đánh dấu các đỉnh đã được nới lỏng),


Để biết thêm chi tiết triển khai và kết nối của phiên bản 3 với thuật toán Bellman-Ford, vui lòng xem câu trả lời này từ zhihu . Đó cũng là câu trả lời của tôi (nhưng bằng tiếng Trung). Hiện tại tôi không có thời gian để dịch nó sang tiếng Anh. Tôi thực sự đánh giá cao nếu ai đó có thể làm điều này và chỉnh sửa câu trả lời này trên stackoverflow.


1

Hãy xem xét điều gì sẽ xảy ra nếu bạn qua lại giữa B và C ... thì đấy

(chỉ có liên quan nếu biểu đồ không có hướng)

Đã chỉnh sửa: Tôi tin rằng vấn đề liên quan đến thực tế là đường đi với AC * chỉ có thể tốt hơn AB với sự tồn tại của các cạnh trọng số âm, vì vậy bạn đi đâu sau AC, với giả thiết là không Các cạnh trọng lượng âm không thể tìm thấy con đường tốt hơn AB một khi bạn đã chọn đến B sau khi đi AC.


điều này là không thể, đồ thị có hướng.
amit

@amit: điểm tốt, tôi đã bỏ lỡ điều đó. Thời gian để xem xét lại vấn đề
prusswan

1

"2) Chúng ta có thể sử dụng thuật toán Dijksra để tìm đường đi ngắn nhất cho đồ thị có trọng số âm không - một ý tưởng có thể là, tính giá trị trọng số tối thiểu, thêm giá trị dương (bằng giá trị tuyệt đối của giá trị trọng số tối thiểu) cho tất cả các trọng số và chạy thuật toán Dijksra cho biểu đồ đã sửa đổi. Liệu thuật toán này có hoạt động không? "

Điều này hoàn toàn không hoạt động trừ khi tất cả các đường đi ngắn nhất có cùng độ dài. Ví dụ cho một đường đi ngắn nhất có độ dài hai cạnh, và sau khi cộng giá trị tuyệt đối cho mỗi cạnh, thì tổng chi phí đường đi sẽ tăng lên 2 * | trọng số âm max |. Mặt khác, một đường đi khác có độ dài ba cạnh, do đó chi phí đường đi tăng lên 3 * | trọng số âm max |. Do đó, tất cả các đường dẫn riêng biệt đều tăng lên theo số lượng khác nhau.


0

Bạn có thể sử dụng thuật toán 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 đi độ phức tạp về thời gian nhanh chóng.

Trong trường hợp đó, tôi thấy tốt hơn nên 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.