น้ำหนักติดลบโดยใช้ Algorithm ของ Dijkstra


113

ฉันพยายามทำความเข้าใจว่าทำไมอัลกอริทึมของ Dijkstra จึงไม่ทำงานกับน้ำหนักเชิงลบ อ่านตัวอย่างเกี่ยวกับเส้นทางที่สั้นที่สุดฉันกำลังพยายามหาสถานการณ์ต่อไปนี้:

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

จากเว็บไซต์:

สมมติว่าขอบทั้งหมดชี้จากซ้ายไปขวาหากเราเริ่มต้นด้วย A อัลกอริทึมของ Dijkstra จะเลือกขอบ (A, x) โดยย่อขนาด d (A, A) + ความยาว (ขอบ) คือ (A, B) จากนั้นตั้งค่า d (A, B) = 2 และเลือกขอบอื่น (y, C) ลดขนาด d (A, y) + d (y, C); ทางเลือกเดียวคือ (A, C) และตั้งค่า d (A, C) = 3 แต่ไม่เคยพบเส้นทางที่สั้นที่สุดจาก A ถึง B ผ่าน C โดยมีความยาวทั้งหมด 1

ฉันไม่เข้าใจว่าทำไมการใช้ Dijkstra ต่อไปนี้ d [B] จะไม่ได้รับการอัปเดตเป็น1(เมื่ออัลกอริทึมถึงจุดยอด C มันจะทำงานแบบผ่อนคลายบน B ดูว่า d [B] เท่ากับ2และจึงอัปเดต มูลค่าเป็น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)
}

ขอบคุณ

เมียร์


การเบิกทางโดยทั่วไปด้วยน้ำหนักขอบลบเป็นเรื่องยากมาก ไม่ว่าคุณจะพบเส้นทางใดก็ตามมีความเป็นไปได้เสมอที่จะเป็นเส้นทางที่ยาวโดยพลการโดยมีน้ำหนักขอบลบที่มากตามอำเภอใจ ฉันจะไม่แปลกใจถ้า NP สมบูรณ์
Nick Johnson

4
สำหรับใครก็ตามที่มีข้อสงสัยนี้คุณสามารถค้นหาเส้นทางที่สั้นที่สุดในกราฟ GIVEN ที่ไม่มีรอบน้ำหนักติดลบ อัลกอริทึมข้างต้นจะใช้งานได้หากฟังก์ชัน Relax ส่งคืนค่า "จริง" เมื่อการผ่อนคลายประสบความสำเร็จจริงในกรณีนี้จุดยอด "v" ที่อยู่ติดกันจะถูกจัดคิวไว้ในลำดับความสำคัญหากไม่มีอยู่หรืออัปเดตหากมีอยู่แล้ว ซึ่งหมายความว่าคุณสามารถเพิ่มโหนดที่เยี่ยมชมไปยังคิวลำดับความสำคัญได้อีกครั้งเมื่อพวกเขาเริ่มผ่อนคลาย
goelakash

คำตอบ:


202

อัลกอริทึมที่คุณแนะนำจะค้นหาเส้นทางที่สั้นที่สุดในกราฟนี้ แต่ไม่ใช่กราฟทั้งหมดโดยทั่วไป ตัวอย่างเช่นพิจารณากราฟนี้:

รูปกราฟ

สมมติว่าขอบถูกส่งจากซ้ายไปขวาตามตัวอย่างของคุณ

อัลกอริทึมของคุณจะทำงานดังนี้:

  1. ครั้งแรกที่คุณตั้งค่าd(A)การzeroและระยะทางอื่น ๆ infinityเพื่อ
  2. จากนั้นคุณขยายโหนดA, การตั้งค่าd(B)การ1, d(C)การzeroและการd(D)99
  3. ต่อไปคุณจะขยายออกCโดยไม่มีการเปลี่ยนแปลงสุทธิ
  4. จากนั้นคุณจะขยายออกBซึ่งไม่มีผลใด ๆ
  5. สุดท้ายคุณขยายDซึ่งการเปลี่ยนแปลงไปd(B)-201

ขอให้สังเกตว่าในตอนท้ายของเรื่องนี้ แต่ที่d(C)ยังคง0, แม้ว่าเส้นทางที่สั้นที่สุดที่จะมีความยาวC -200 อัลกอริทึมของคุณจึงไม่สามารถคำนวณระยะทางได้อย่างแม่นยำในบางกรณี นอกจากนี้แม้ว่าคุณจะชี้กลับร้านบอกว่าจะได้รับจากแต่ละโหนดไปยังโหนดเริ่มต้นAที่คุณต้องการยุติการกลับเส้นทางที่ผิดจากการCA


35
เพื่อเพิ่มคำตอบที่ยอดเยี่ยมของคุณ: Dijkstra เป็นอัลกอริธึมที่ละโมบเป็นสาเหตุของการเลือกสายตาสั้น
blubb

4
ฉันอยากจะชี้ให้เห็นว่าในทางเทคนิคแล้วเส้นทางทั้งหมดในกราฟนี้มีค่าใช้จ่ายของความอนุเคราะห์เชิงลบของวัฏจักรเชิงลบ A, D, B, A
เนท

2
@ เนท - เพื่อความชัดเจนขอบทั้งหมดในกราฟจะถูกส่งจากซ้ายไปขวา มันค่อนข้างยากที่จะแสดงผลลูกศรในงานศิลปะ ASCII คุณภาพสูงของฉัน :-)
templatetypedef

2
สำหรับผู้ที่ไม่เคยเห็นกราฟที่มีขอบลบมาก่อนฉันพบว่าการตีความกราฟนี้เป็นเครือข่ายของถนนที่เก็บค่าผ่านทางที่มีประโยชน์ซึ่งน้ำหนักขอบจะให้ค่าผ่านทางที่คุณจ่าย ถนน -300 เป็นถนนที่เก็บค่าผ่านทางที่บ้าคลั่งซึ่งพวกเขาให้เงินคุณ 300 เหรียญแทน
D Coetzee

3
@ SchwitJanwityanujit- นี่คือวิธีการทำงานของอัลกอริทึมของ Dijkstra อัลกอริทึมไม่ได้สำรวจเส้นทางแต่ทำงานโดยการประมวลผลโหนดแทน แต่ละโหนดจะได้รับการประมวลผลเพียงครั้งเดียวดังนั้นทันทีที่เราประมวลผลโหนด B และได้รับว่าระยะทางคือ 1 เราจะไม่กลับไปที่โหนด B หรือพยายามอัปเดตระยะทาง
templatetypedef

25

โปรดทราบว่า Dijkstra ใช้งานได้แม้กับน้ำหนักที่เป็นลบหากกราฟไม่มีวัฏจักรที่เป็นลบนั่นคือรอบที่มีน้ำหนักสรุปน้อยกว่าศูนย์

แน่นอนว่าอาจมีคนถามว่าทำไมในตัวอย่างที่สร้างโดย templatetypedef Dijkstra จึงล้มเหลวแม้ว่าจะไม่มีรอบที่เป็นลบ แต่ก็ไม่มีการเปลี่ยนแปลงแม้แต่รอบ นั่นเป็นเพราะเขาใช้เกณฑ์การหยุดอื่นซึ่งจะเก็บอัลกอริทึมทันทีที่ถึงโหนดเป้าหมาย (หรือโหนดทั้งหมดถูกตัดสินเพียงครั้งเดียวเขาไม่ได้ระบุอย่างนั้นอย่างแน่นอน) ในกราฟที่ไม่มีน้ำหนักติดลบสิ่งนี้ใช้ได้ดี

หากมีคนใช้เกณฑ์การหยุดทางเลือกซึ่งจะหยุดอัลกอริทึมเมื่อลำดับความสำคัญ - คิว (ฮีป) ทำงานว่างเปล่า (ใช้เกณฑ์การหยุดนี้ในคำถามด้วย) dijkstra จะหาระยะทางที่ถูกต้องแม้กระทั่งสำหรับกราฟที่มีน้ำหนักติดลบ แต่ไม่มี รอบลบ

อย่างไรก็ตามในกรณีนี้เวลาที่ไม่แสดงอาการของ dijkstra สำหรับกราฟที่ไม่มีรอบเชิงลบจะหายไป เนื่องจากโหนดที่ถูกตัดสินก่อนหน้านี้สามารถใส่เข้าไปในฮีปอีกครั้งเมื่อพบระยะทางที่ดีขึ้นเนื่องจากน้ำหนักติดลบ คุณสมบัตินี้เรียกว่าการแก้ไขฉลาก


2. ไม่ชัดเจนว่าทำไมคุณถึงคิดว่าเวลาจะ "เหมือนเบลล์แมน - ฟอร์ด" มากกว่าและไม่ใช่เลขชี้กำลัง (ซึ่งแย่กว่าเบลล์แมน - ฟอร์ด) คุณมีอัลกอริทึมที่เป็นรูปธรรมและมีหลักฐานในใจหรือไม่?
กัซซ่า

3
ถึง 1: ในขณะที่คุณสามารถใช้การใช้งาน dijkstra แบบเดียวกันกับเกณฑ์การหยุดที่กล่าวถึงซึ่งจะหยุดเมื่อคิวทำงานว่างเปล่า (ดูรหัสเทียมในคำถามเดิม) แต่ยังคงเป็นอัลกอริทึม dijkstras สำหรับเส้นทางที่สั้นที่สุดแม้ว่าจะทำงานแตกต่างกัน การตั้งค่าโหนดหลาย ๆ ครั้ง (การแก้ไขฉลาก)
infty10000101

1
ถึง 2: นั่นเป็นเพียงการคาดเดาดังนั้นฉันจะลบมันออก ฉันคิดว่าคุณพูดถูกกับเวลาเอกซ์โพเนนเชียลเนื่องจากมีเส้นทางจำนวนมากที่ต้องสำรวจ
infty10000101

11

คุณไม่ได้ใช้ S ที่ใดก็ได้ในอัลกอริทึมของคุณ (นอกเหนือจากการแก้ไข) ความคิดของ dijkstra เมื่อจุดยอดอยู่บน S แล้วจะไม่มีการแก้ไขอีกเลย ในกรณีนี้เมื่อ B อยู่ใน S แล้วคุณจะไม่สามารถเข้าถึงได้อีกทาง C

ข้อเท็จจริงนี้ทำให้มั่นใจได้ถึงความซับซ้อนของ O (E + VlogV) [มิฉะนั้นคุณจะทำขอบซ้ำอีกครั้งและจุดยอดอีกครั้งหนึ่ง]

กล่าวอีกนัยหนึ่งอัลกอริทึมที่คุณโพสต์อาจไม่อยู่ใน O (E + VlogV) ตามที่อัลกอริทึมของ dijkstra สัญญาไว้


นอกจากนี้ไม่จำเป็นต้องแก้ไขจุดยอดโดยไม่มีขอบน้ำหนักติดลบซึ่งทำลายสมมติฐานที่ว่าต้นทุนเส้นทางสามารถเพิ่มขึ้นได้เมื่อมีขอบซ้ำเท่านั้น
prusswan

สมมติฐานนี้เป็นสิ่งที่ทำให้เราสามารถใช้ S ได้และ 'รู้' เมื่อจุดยอดอยู่ใน S แล้วจะไม่มีการแก้ไขอีก
Amit

คำพูดสุดท้ายของคุณผิด อัลกอริทึมที่โพสต์มีความซับซ้อนของเวลา O (E + VlogV) เมื่อทำงานบนกราฟที่ไม่มีขอบด้านลบ ไม่จำเป็นต้องตรวจสอบว่าเราได้เยี่ยมชมโหนดแล้วเนื่องจากข้อเท็จจริงที่ว่ามีการเยี่ยมชมแล้วจะรับประกันได้ว่าขั้นตอนการผ่อนคลายจะไม่เพิ่มอีกครั้งในคิว
Pixar

7

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


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


4

TL; DR: คำตอบขึ้นอยู่กับการใช้งานของคุณ สำหรับรหัสหลอกที่คุณโพสต์จะใช้ได้กับน้ำหนักติดลบ


ตัวแปรของ Algorithm ของ Dijkstra

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

  1. ใช้-loop ซ้อนกันforเพื่อผ่อนคลายจุดยอด นี่เป็นวิธีที่ง่ายที่สุดในการใช้อัลกอริทึมของ Dijkstra ความซับซ้อนของเวลาคือ O (V ^ 2)
  2. การใช้งานตามลำดับความสำคัญคิว / ฮีป + ไม่อนุญาตให้เข้าใหม่โดยที่การเข้าใหม่หมายถึงจุดยอดที่ผ่อนคลายสามารถถูกผลักเข้าไปในลำดับความสำคัญอีกครั้งเพื่อให้ผ่อนคลายอีกครั้งในภายหลังอีกทางเข้าหมายถึงจุดสุดยอดผ่อนคลายสามารถผลักดันให้เป็นลำดับความสำคัญคิวอีกครั้งเพื่อจะผ่อนคลายอีกครั้งในภายหลัง
  3. การใช้งานตามลำดับความสำคัญคิว / ฮีป + อนุญาตให้เข้าใหม่

เวอร์ชัน 1 และ 2 จะล้มเหลวในกราฟที่มีน้ำหนักเชิงลบ (หากคุณได้รับคำตอบที่ถูกต้องในกรณีเช่นนี้มันเป็นเพียงเรื่องบังเอิญ) แต่เวอร์ชัน 3 ยังคงใช้งานได้ได้

รหัสหลอกที่โพสต์ภายใต้ปัญหาเดิมคือเวอร์ชัน 3 ด้านบนดังนั้นจึงใช้ได้กับน้ำหนักติดลบ

นี่คือข้อมูลอ้างอิงที่ดีจากAlgorithm (รุ่นที่ 4)ซึ่งระบุว่า (และมีการใช้งาน java ของเวอร์ชัน 2 และ 3 ที่ฉันกล่าวถึงข้างต้น):

ถาม: อัลกอริทึมของ Dijkstra ทำงานกับน้ำหนักเชิงลบหรือไม่

ตอบใช่และไม่ใช่ มีอัลกอริธึมเส้นทางที่สั้นที่สุดสองแบบที่เรียกว่าอัลกอริทึมของ Dijkstra ขึ้นอยู่กับว่าจุดยอดสามารถจัดลำดับในคิวลำดับความสำคัญได้มากกว่าหนึ่งครั้งหรือไม่ เมื่อน้ำหนักไม่เป็นค่าลบทั้งสองเวอร์ชันจะเกิดขึ้นพร้อมกัน (เนื่องจากจะไม่มีจุดยอดมากกว่าหนึ่งครั้ง) เวอร์ชันที่ใช้ใน DijkstraSP.java (ซึ่งอนุญาตให้จุดยอดถูกจัดคิวได้มากกว่าหนึ่งครั้ง) ถูกต้องเมื่อมีน้ำหนักขอบลบ (แต่ไม่มีรอบเชิงลบ) แต่เวลาในการทำงานเป็นเลขชี้กำลังในกรณีที่เลวร้ายที่สุด (โปรดทราบว่า DijkstraSP.java จะมีข้อยกเว้นหาก digraph ที่เน้นขอบมีขอบที่มีน้ำหนักเป็นลบดังนั้นโปรแกรมเมอร์จึงไม่แปลกใจกับพฤติกรรมเลขชี้กำลังนี้) หากเราแก้ไข DijkstraSP.java เพื่อไม่ให้จุดยอด มากกว่าหนึ่งครั้ง (เช่นใช้อาร์เรย์ที่ทำเครื่องหมาย [] เพื่อทำเครื่องหมายจุดยอดเหล่านั้นที่ผ่อนคลายแล้ว)


สำหรับรายละเอียดการดำเนินงานมากขึ้นและการเชื่อมต่อของ 3 รุ่นด้วย Bellman ฟอร์ดอัลกอริทึมโปรดดูคำตอบนี้จาก zhihu ยังเป็นคำตอบของฉัน (แต่เป็นภาษาจีน) ตอนนี้ฉันไม่มีเวลาแปลเป็นภาษาอังกฤษ ฉันจะขอบคุณมากถ้ามีใครสามารถทำได้และแก้ไขคำตอบนี้ใน stackoverflow


1

พิจารณาว่าจะเกิดอะไรขึ้นถ้าคุณไปมาระหว่าง B และ C ... voila

(เกี่ยวข้องเฉพาะในกรณีที่ไม่ได้กำกับกราฟ)

แก้ไข: ฉันเชื่อว่าปัญหาเกี่ยวข้องกับความจริงที่ว่าเส้นทางที่มี AC * จะดีกว่า AB เท่านั้นเมื่อมีขอบน้ำหนักติดลบดังนั้นจึงไม่สำคัญว่าคุณจะไปที่ใดหลังจาก AC ด้วยสมมติฐานที่ไม่ใช่ - ขอบน้ำหนักติดลบเป็นไปไม่ได้ที่จะหาเส้นทางที่ดีกว่า AB เมื่อคุณเลือกที่จะไปถึง B หลังจากไป AC


เป็นไปไม่ได้กราฟถูกกำหนดทิศทาง
Amit

@amit: จุดที่ดีฉันพลาดสิ่งนั้น ใช้เวลาในการพิจารณาปัญหาใหม่
prusswan

1

"2) เราสามารถใช้อัลกอริทึมของ Dijksra สำหรับเส้นทางที่สั้นที่สุดสำหรับกราฟที่มีน้ำหนักเชิงลบได้หรือไม่ - แนวคิดหนึ่งสามารถคำนวณค่าน้ำหนักขั้นต่ำเพิ่มค่าบวก (เท่ากับค่าสัมบูรณ์ของค่าน้ำหนักขั้นต่ำ) ให้กับน้ำหนักทั้งหมดและเรียกใช้อัลกอริทึมของ Dijksra สำหรับกราฟที่แก้ไขอัลกอริทึมนี้จะใช้ได้หรือไม่ "

วิธีนี้ใช้ไม่ได้อย่างแน่นอนเว้นแต่เส้นทางที่สั้นที่สุดทั้งหมดจะมีความยาวเท่ากัน ตัวอย่างเช่นกำหนดเส้นทางที่สั้นที่สุดของความยาวสองขอบและหลังจากเพิ่มค่าสัมบูรณ์ให้กับแต่ละขอบแล้วต้นทุนรวมของเส้นทางจะเพิ่มขึ้น 2 * | น้ำหนักลบสูงสุด | ในทางกลับกันอีกเส้นทางหนึ่งที่มีความยาวสามขอบดังนั้นต้นทุนเส้นทางจึงเพิ่มขึ้น 3 * | น้ำหนักลบสูงสุด |. ดังนั้นเส้นทางที่แตกต่างกันทั้งหมดจะเพิ่มขึ้นตามจำนวนที่แตกต่างกัน


0

คุณสามารถใช้อัลกอริทึมของ dijkstra ที่มีขอบลบไม่รวมถึงวัฏจักรเชิงลบ แต่คุณต้องอนุญาตให้สามารถเยี่ยมชมจุดยอดได้หลายครั้งและเวอร์ชันนั้นจะสูญเสียความซับซ้อนของเวลาที่รวดเร็ว

ในกรณีนี้ฉันเห็นว่าควรใช้อัลกอริธึม SPFAซึ่งมีคิวปกติและสามารถจัดการกับขอบลบได้ดีกว่า

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