Breadth-First Search ทำงานอย่างไรเมื่อมองหา Shortest Path


132

ฉันได้ทำการค้นคว้าและดูเหมือนว่าฉันจะขาดส่วนเล็ก ๆ ส่วนหนึ่งของอัลกอริทึมนี้ ฉันเข้าใจวิธีการทำงานของการค้นหาแบบกว้าง - แรก แต่ฉันไม่เข้าใจว่ามันจะพาฉันไปยังเส้นทางที่เจาะจงได้อย่างไรแทนที่จะบอกเพียงว่าแต่ละโหนดไปที่ใดได้ ฉันเดาว่าวิธีที่ง่ายที่สุดในการอธิบายความสับสนของฉันคือการให้ตัวอย่าง:

ตัวอย่างเช่นสมมติว่าฉันมีกราฟดังนี้:

ใส่คำอธิบายภาพที่นี่

และเป้าหมายของฉันคือการได้รับจาก A ถึง E (ขอบทั้งหมดไม่ได้ถ่วงน้ำหนัก)

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

ฉันเลิกจัดคิว B และสำรวจมันและพบว่ามันนำไปสู่ ​​A (สำรวจแล้ว) และ C ดังนั้นฉันจึงจัดคิว C ฉันจึงออกคิว D และพบว่ามันนำไปสู่ ​​E เป้าหมายของฉัน จากนั้นฉันก็ยกเลิกคิว C และพบว่ามันนำไปสู่ ​​E เช่นกัน

ฉันรู้อย่างมีเหตุผลว่าเส้นทางที่เร็วที่สุดคือ A-> D-> E แต่ฉันไม่แน่ใจว่าการค้นหาอันดับแรกจะช่วยได้อย่างไร - ฉันจะบันทึกเส้นทางเช่นนั้นได้อย่างไรเมื่อฉันเสร็จสิ้นฉันสามารถวิเคราะห์ผลลัพธ์และดู เส้นทางที่สั้นที่สุดคือ A-> D-> E?

นอกจากนี้โปรดทราบว่าฉันไม่ได้ใช้ต้นไม้จริงๆจึงไม่มีโหนด "หลัก" มีเพียงลูกเท่านั้น


2
"นอกจากนี้โปรดทราบว่าฉันไม่ได้ใช้ต้นไม้จริงๆดังนั้นจึงไม่มีโหนด" พาเรนต์ "มี แต่เด็ก" แน่นอนว่าคุณจะต้องเก็บพ่อแม่ไว้ที่ไหนสักแห่ง สำหรับ DFS คุณกำลังดำเนินการทางอ้อมผ่าน call stack สำหรับ BFS คุณต้องทำอย่างชัดเจน คุณไม่สามารถทำอะไรได้ฉันกลัว :)
Voo

คำตอบ:


85

ในทางเทคนิคการค้นหาแบบกว้างเป็นอันดับแรก (BFS) นั้นไม่ได้ช่วยให้คุณพบเส้นทางที่สั้นที่สุดเพียงเพราะ BFS ไม่ได้มองหาเส้นทางที่สั้นที่สุด: BFS อธิบายถึงกลยุทธ์ในการค้นหากราฟ แต่ไม่ได้บอกว่าคุณต้องค้นหา โดยเฉพาะอย่างยิ่ง

อัลกอริทึมของ Dijkstraปรับ BFS เพื่อให้คุณค้นหาเส้นทางที่สั้นที่สุดของแหล่งเดียว

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

หากฟังดูสับสนเล็กน้อย wikipedia ก็มีส่วนรหัสเทียมที่ดีในหัวข้อนี้


ขอบคุณ! ฉันได้อ่านรหัสเทียมก่อนหน้านี้ แต่ไม่เข้าใจคำอธิบายของคุณทำให้คลิกเพื่อฉัน
Jake

46
ฉันต้องการบันทึกต่อไปนี้สำหรับผู้ที่ดูโพสต์นี้ในอนาคต: หากขอบไม่ได้รับการถ่วงน้ำหนักก็ไม่จำเป็นต้องจัดเก็บ "ระยะทางที่สั้นที่สุดในปัจจุบัน" สำหรับแต่ละโหนด สิ่งที่ต้องจัดเก็บคือพาเรนต์สำหรับแต่ละโหนดที่ค้นพบ ดังนั้นในขณะที่คุณกำลังตรวจสอบโหนดและจัดคิวผู้สืบทอดทั้งหมดเพียงตั้งค่าพาเรนต์ของโหนดเหล่านั้นไปยังโหนดที่คุณกำลังตรวจสอบ (ซึ่งจะเพิ่มเป็นสองเท่าเมื่อทำเครื่องหมายว่า "ค้นพบ") หากตัวชี้พาเรนต์นี้เป็น NUL / nil / None สำหรับโหนดใดก็ตามหมายความว่า BFS ยังไม่ถูกค้นพบหรือเป็นโหนดต้นทาง / รูทเอง
Shashank

@Shashank ถ้าเราไม่รักษาระยะทางแล้วเราจะรู้ระยะทางที่สั้นที่สุดได้อย่างไรโปรดอธิบายเพิ่มเติม
Gaurav Sehgal

12
@gauravsehgal ความคิดเห็นนั้นมีไว้สำหรับกราฟที่มีขอบไม่ถ่วงน้ำหนัก BFS จะหาระยะทางที่สั้นที่สุดเพียงเพราะรูปแบบการค้นหาตามแนวรัศมีซึ่งพิจารณาโหนดตามลำดับระยะทางจากจุดเริ่มต้น
Shashank

13
เคล็ดลับสำหรับผู้อ่านความคิดเห็นของ @ Shashank: ไม่ถ่วงน้ำหนักและถ่วงน้ำหนักสม่ำเสมอ (เช่นขอบทั้งหมดมีน้ำหนัก = 5) เทียบเท่า
TWiStErRob

55

เป็นแหลมข้างต้น BFS สามารถเพียงใช้ในการหาเส้นทางที่สั้นที่สุดในกราฟถ้า:

  1. ไม่มีการวนซ้ำ

  2. ขอบทั้งหมดมีน้ำหนักเท่ากันหรือไม่มีน้ำหนัก

ในการค้นหาเส้นทางที่สั้นที่สุดสิ่งที่คุณต้องทำคือเริ่มจากต้นทางและทำการค้นหาแบบกว้าง ๆ ก่อนและหยุดเมื่อคุณพบโหนดปลายทางของคุณ สิ่งเดียวที่คุณต้องทำเพิ่มเติมคือมีอาร์เรย์ก่อนหน้า [n] ซึ่งจะเก็บโหนดก่อนหน้าสำหรับทุกโหนดที่เข้าชม ก่อนหน้าของแหล่งที่มาสามารถเป็นโมฆะ

ในการพิมพ์เส้นทางให้วนลูปผ่านอาร์เรย์ [] ก่อนหน้าจากต้นทางจนกระทั่งถึงปลายทางและพิมพ์โหนด DFS ยังสามารถใช้เพื่อค้นหาเส้นทางที่สั้นที่สุดในกราฟภายใต้เงื่อนไขที่คล้ายคลึงกัน

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


1
Dijkstra ถ้าไม่มีน้ำหนักอย่างอื่นให้ใช้ Bellman ford algo if -ve weights
shaunak1111

BFS ทำงานเพื่อค้นหาเส้นทางที่สั้นที่สุดระหว่างสองโหนดหรือไม่
Maria Ines Parnisari

35
@javaProgrammer มันไม่ถูกต้อง BFS สามารถใช้เพื่อค้นหาเส้นทางที่สั้นที่สุดในกราฟวัฏจักรที่ไม่ถ่วงน้ำหนักได้เช่นกัน หากกราฟไม่ได้รับการถ่วงน้ำหนักคุณสามารถใช้ BFS สำหรับ SP ได้โดยไม่คำนึงถึงการวนซ้ำ
Andrei Kaigorodov

3
To print the path, simple loop through the previous[] array from source till you reach destination.แต่แหล่งที่มาก่อนหน้านี้เป็นโมฆะ backtrace from destination using the previous array until you reach the sourceผมคิดว่าสิ่งที่คุณหมายคือ
aandis

2
ทำไมคุณถึงบอกว่า DFS จะทำงานภายใต้เงื่อนไขที่คล้ายกัน? เป็นไปไม่ได้หรือที่ DFS จะใช้เส้นทางรอบที่ไร้เดียงสาเพื่อเดินทางจากโหนด start-> end ดังนั้นจึงให้เส้นทางที่ไม่สั้นที่สุด?
James Wierzba

26

จากบทแนะนำที่นี่

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


สิ่งนี้เหมาะสำหรับโหนดที่เข้าถึงได้โดยตรง (1-> 2) (2 เข้าถึงได้โดยตรงจาก 1) สำหรับโหนดที่ไม่สามารถเข้าถึงได้โดยตรงมีงานมากขึ้น (1-> 2-> 3, 3 ไม่ถึงโดยตรงจาก 1) แน่นอนว่ามันยังคงเป็นจริงเมื่อพิจารณาทีละรายการกล่าวคือ 1-> 2 & 2-> 3 ทีละเส้นทางเป็นเส้นทางที่สั้นที่สุด
Manohar Reddy Poreddy

12

ในที่สุดฉันเสียเวลา 3 วัน
ในการแก้ไขคำถามเกี่ยวกับกราฟที่
ใช้ใน
การหาระยะทางที่สั้นที่สุด
โดยใช้ BFS

อยากแชร์ประสบการณ์.

When the (undirected for me) graph has
fixed distance (1, 6, etc.) for edges

#1
We can use BFS to find shortest path simply by traversing it
then, if required, multiply with fixed distance (1, 6, etc.)

#2
As noted above
with BFS
the very 1st time an adjacent node is reached, it is shortest path

#3
It does not matter what queue you use
   deque/queue(c++) or
   your own queue implementation (in c language)
   A circular queue is unnecessary

#4
Number of elements required for queue is N+1 at most, which I used
(dint check if N works)
here, N is V, number of vertices.

#5
Wikipedia BFS will work, and is sufficient.
    https://en.wikipedia.org/wiki/Breadth-first_search#Pseudocode

ฉันเสียเวลาไป 3 วันในการลองใช้ทางเลือกอื่นทั้งหมดข้างต้นการตรวจสอบและยืนยันซ้ำอีกครั้งแล้วครั้งเล่าข้างต้น
ไม่ใช่ปัญหา
(พยายามใช้เวลาค้นหาปัญหาอื่น ๆ หากคุณไม่พบปัญหาใด ๆ กับข้อ 5 ข้างต้น)


คำอธิบายเพิ่มเติมจากความคิดเห็นด้านล่าง:

      A
     /  \
  B       C
 /\       /\
D  E     F  G

สมมติด้านบนคือกราฟของคุณ
จะลงไปด้านล่าง
สำหรับ A ตัวช่วยคือ B & C
สำหรับ B ตัวช่วยคือ D & E
สำหรับ C ตัวช่วยคือ F & G

พูดว่าโหนดเริ่มต้นคือ A

  1. เมื่อคุณไปถึง A ถึง B & C ระยะทางที่สั้นที่สุดถึง B & C จาก A คือ 1

  2. เมื่อคุณไปถึง D หรือ E ถึง B ระยะทางที่สั้นที่สุดไปยัง A & D คือ 2 (A-> B-> D)

ในทำนองเดียวกัน A-> E คือ 2 (A-> B-> E)

ด้วย A-> F & A-> G คือ 2

ดังนั้นตอนนี้แทนที่จะเป็น 1 ระยะห่างระหว่างโหนดถ้าเป็น 6 ให้คูณคำตอบด้วย 6
ตัวอย่างเช่น
ถ้าระยะห่างระหว่างแต่ละโหนดเป็น 1 ดังนั้น A-> E คือ 2 (A-> B-> E = 1 + 1 )
ถ้าระยะห่างระหว่างแต่ละอันคือ 6 ดังนั้น A-> E คือ 12 (A-> B-> E = 6 + 6)

ใช่ bfs อาจใช้เส้นทางใดก็ได้
แต่เรากำลังคำนวณสำหรับเส้นทางทั้งหมด

ถ้าคุณต้องไปจาก A ถึง Z เราจะเดินทางทุกเส้นทางจาก A ไปยังตัวกลาง I และเนื่องจากจะมีหลายเส้นทางที่เราทิ้งทั้งหมดยกเว้นเส้นทางที่สั้นที่สุดจนถึงฉันจากนั้นเดินต่อด้วยเส้นทางที่สั้นที่สุดไปยังโหนดถัดไป J
อีกครั้งหาก มีหลายเส้นทางจาก I ถึง J เราใช้เวลาเพียง
ตัวอย่างเดียว
สมมติว่า
A -> ฉันมีระยะทาง 5
(STEP) สมมติ I -> J เรามีหลายเส้นทางระยะทาง 7 และ 8 เนื่องจาก 7 สั้นที่สุด
เราใช้ A -> J เป็น 5 (A-> I สั้นที่สุด) + 8 (สั้นที่สุดในขณะนี้) = 13
ดังนั้น A-> J คือ 13 ตอนนี้
เราทำซ้ำตอนนี้ด้านบน (STEP) สำหรับ J -> K ไปเรื่อย ๆ จนกว่าเราจะได้ ถึง Z

อ่านส่วนนี้ 2 หรือ 3 ครั้งแล้ววาดลงบนกระดาษคุณจะได้รับสิ่งที่ฉันพูดขอให้โชคดี



คุณช่วยอธิบายให้ละเอียดได้ไหมว่าคุณหาเส้นทางที่สั้นที่สุดด้วยการค้นหาแรกแบบกว้าง ๆ ได้อย่างไร การค้นหาแบบกว้างแรกค้นหาโหนดเป็นหลักโดยสามารถมี n เส้นทางไปยังโหนดเป้าหมายจากโหนดต้นทางและ bfs อาจใช้เส้นทางใดก็ได้ คุณกำลังกำหนดเส้นทางที่ดีที่สุดอย่างไร?
ตกอับ

ฉันได้เพิ่มส่วน 'คำอธิบายเพิ่มเติม' ไว้ในคำตอบข้างต้นแล้วโปรดแจ้งให้เราทราบว่าตรงตามความต้องการหรือไม่
Manohar Reddy Poreddy

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

คำถามที่ดีโหวตแล้วใช่เราไม่ได้ทิ้งเราติดตามโหนดที่อยู่ติดกันทั้งหมดจนกว่าเราจะไปถึงปลายทาง BFS จะทำงานก็ต่อเมื่อมีระยะทางคงที่เท่านั้นเช่นทั้ง 7 หรือทั้งหมด 8 ฉันให้ค่าทั่วไปซึ่งมี 7 & 8 ซึ่งเรียกอีกอย่างว่าอัลกอริทึมของ dijkstra
Manohar Reddy Poreddy

ขออภัยไม่แน่ใจว่าคุณหมายถึงอะไรดูที่en.wikipedia.org/wiki/Dijkstra's_algorithm
Manohar Reddy Poreddy

2

ขึ้นอยู่กับคำตอบ acheron55ผมโพสต์การดำเนินงานที่เป็นไปได้ที่นี่
นี่คือช่วงเวลาสั้น ๆ ของมัน:

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


0

เยี่ยมชมกระทู้นี้หลังจากไม่มีการใช้งานมาระยะหนึ่ง แต่เนื่องจากฉันไม่เห็นคำตอบอย่างละเอียดนี่คือสองเซ็นต์ของฉัน

การค้นหาแบบกว้างก่อนจะพบเส้นทางที่สั้นที่สุดในกราฟแบบไม่ถ่วงน้ำหนัก กราฟอาจเป็นแบบวนรอบหรือแบบวงจร

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

mark all vertices as unvisited
set the distance value of all vertices to infinity
set the distance value of the start vertex to 0
if the start vertex is the end vertex, return 0
push the start vertex on the queue
while(queue is not empty)   
    dequeue one vertex (well call it x) off of the queue
    if x is not marked as visited:
        mark it as visited
        for all of the unmarked children of x:
            set their distance values to be the distance of x + 1
            if the value of x is the value of the end vertex: 
                return the distance of x
            otherwise enqueue it to the queue
if here: there is no path connecting the vertices

โปรดทราบว่าวิธีนี้ใช้ไม่ได้กับกราฟแบบถ่วงน้ำหนักโปรดดูอัลกอริทึมของ Dijkstra


-7

โซลูชันต่อไปนี้ใช้ได้กับทุกกรณีการทดสอบ

import java.io.*;
import java.util.*;
import java.text.*;
import java.math.*;
import java.util.regex.*;

public class Solution {

   public static void main(String[] args)
        {
            Scanner sc = new Scanner(System.in);

            int testCases = sc.nextInt();

            for (int i = 0; i < testCases; i++)
            {
                int totalNodes = sc.nextInt();
                int totalEdges = sc.nextInt();

                Map<Integer, List<Integer>> adjacencyList = new HashMap<Integer, List<Integer>>();

                for (int j = 0; j < totalEdges; j++)
                {
                    int src = sc.nextInt();
                    int dest = sc.nextInt();

                    if (adjacencyList.get(src) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(src);
                        neighbours.add(dest);
                        adjacencyList.put(src, neighbours);
                    }


                    if (adjacencyList.get(dest) == null)
                    {
                        List<Integer> neighbours = new ArrayList<Integer>();
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    } else
                    {
                        List<Integer> neighbours = adjacencyList.get(dest);
                        neighbours.add(src);
                        adjacencyList.put(dest, neighbours);
                    }
                }

                int start = sc.nextInt();

                Queue<Integer> queue = new LinkedList<>();

                queue.add(start);

                int[] costs = new int[totalNodes + 1];

                Arrays.fill(costs, 0);

                costs[start] = 0;

                Map<String, Integer> visited = new HashMap<String, Integer>();

                while (!queue.isEmpty())
                {
                    int node = queue.remove();

                    if(visited.get(node +"") != null)
                    {
                        continue;
                    }

                    visited.put(node + "", 1);

                    int nodeCost = costs[node];

                    List<Integer> children = adjacencyList.get(node);

                    if (children != null)
                    {
                        for (Integer child : children)
                        {
                            int total = nodeCost + 6;
                            String key = child + "";

                            if (visited.get(key) == null)
                            {
                                queue.add(child);

                                if (costs[child] == 0)
                                {
                                    costs[child] = total;
                                } else if (costs[child] > total)
                                {
                                    costs[child] = total;
                                }
                            }
                        }
                    }
                }

                for (int k = 1; k <= totalNodes; k++)
                {
                    if (k == start)
                    {
                        continue;
                    }

                    System.out.print(costs[k] == 0 ? -1 : costs[k]);
                    System.out.print(" ");
                }
                System.out.println();
            }
        }
}

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