Làm thế nào để tìm kiếm Breadth-First hoạt động khi tìm kiếm Đường dẫn ngắn nhất?


129

Tôi đã thực hiện một số nghiên cứu và dường như tôi đang thiếu một phần nhỏ của thuật toán này. Tôi hiểu cách mà Breadth-First Search hoạt động, nhưng tôi không hiểu chính xác nó sẽ đưa tôi đến một con đường cụ thể như thế nào, trái ngược với việc chỉ cho tôi biết mỗi nút riêng lẻ có thể đi đến đâu. Tôi đoán cách dễ nhất để giải thích sự nhầm lẫn của tôi là cung cấp một ví dụ:

Vì vậy, ví dụ, giả sử tôi có một biểu đồ như thế này:

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

Và mục tiêu của tôi là đi từ A đến E (tất cả các cạnh đều không có trọng số).

Tôi bắt đầu tại A, vì đó là nguồn gốc của tôi. Tôi xếp hàng A, theo sau là ngay lập tức A và khám phá nó. Điều này mang lại B và D, vì A được kết nối với B và D. Do đó, tôi xếp hàng cả B và D.

Tôi dequeue B và khám phá nó, và thấy rằng nó dẫn đến A (đã khám phá) và C, vì vậy tôi xếp hàng C. Tôi sau đó dequeue D, và thấy rằng nó dẫn đến E, mục tiêu của tôi. Sau đó tôi dequeue C, và thấy rằng nó cũng dẫn đến E, mục tiêu của tôi.

Tôi biết một cách logic rằng đường dẫn nhanh nhất là A-> D-> E, nhưng tôi không chắc chính xác việc tìm kiếm đầu tiên giúp ích như thế nào - tôi nên ghi lại các đường dẫn như thế nào khi tôi kết thúc, tôi có thể phân tích kết quả và xem con đường ngắn nhất là A-> D-> E?

Ngoài ra, lưu ý rằng tôi không thực sự sử dụng cây, vì vậy không có nút "cha mẹ", chỉ có con.


2
"Ngoài ra, lưu ý rằng tôi đang sử dụng không thực sự sử dụng cây, vì vậy không có nút" cha mẹ ", chỉ có con" - rõ ràng bạn sẽ phải lưu trữ cha mẹ ở đâu đó. Đối với DFS, bạn đang thực hiện gián tiếp thông qua ngăn xếp cuộc gọi, đối với BFS, bạn phải thực hiện một cách rõ ràng. Tôi không có gì bạn có thể làm về điều đó tôi sợ :)
Voo

Câu trả lời:


85

Về mặt kỹ thuật, bản thân tìm kiếm đầu tiên (BFS) không cho phép bạn tìm thấy con đường ngắn nhất, đơn giản vì BFS không tìm kiếm một con đường ngắn nhất: BFS mô tả chiến lược tìm kiếm biểu đồ, nhưng nó không nói rằng bạn phải tìm kiếm bất cứ cái gì một cách cụ thể.

Thuật toán của Dijkstra điều chỉnh BFS để cho phép bạn tìm các đường dẫn ngắn nhất nguồn đơn.

Để truy xuất đường dẫn ngắn nhất từ ​​điểm gốc đến nút, bạn cần duy trì hai mục cho mỗi nút trong biểu đồ: khoảng cách ngắn nhất hiện tại của nó và nút trước trong đường dẫn ngắn nhất. Ban đầu tất cả các khoảng cách được đặt thành vô cùng, và tất cả các tiền thân được đặt thành trống. Trong ví dụ của bạn, bạn đặt khoảng cách của A thành 0, rồi tiếp tục với BFS. Trên mỗi bước bạn kiểm tra xem bạn có thể cải thiện khoảng cách của con cháu hay không, tức là khoảng cách từ gốc đến gốc trước cộng với độ dài của cạnh mà bạn đang khám phá nhỏ hơn khoảng cách tốt nhất hiện tại cho nút đang đề cập. Nếu bạn có thể cải thiện khoảng cách, hãy đặt đường dẫn ngắn nhất mới và ghi nhớ tiền thân thông qua đó đường dẫn đó đã được mua. Khi hàng đợi BFS trống, chọn một nút (trong ví dụ của bạn, đó là E) và duyệt qua các tiền thân của nó trở về điểm gốc.

Nếu điều này nghe có vẻ hơi khó hiểu, wikipedia có một phần mã giả đẹp về chủ đề này.


Cảm ơn bạn! Tôi đã đọc qua mã giả trước đó nhưng không thể hiểu được, lời giải thích của bạn đã khiến nó nhấp cho tôi
Jake

46
Tôi muốn đưa ra lưu ý sau cho những người nhìn vào bài đăng này trong tương lai: Nếu các cạnh không có trọng số, không cần lưu trữ "khoảng cách ngắn nhất hiện tại" cho mỗi nút. Tất cả những gì cần được lưu trữ là cha mẹ cho mỗi nút được phát hiện. Do đó, trong khi bạn đang kiểm tra một nút và liệt kê tất cả các thành công của nó, chỉ cần đặt cha của các nút đó thành nút mà bạn đang kiểm tra (điều này sẽ tăng gấp đôi khi đánh dấu chúng "được phát hiện"). Nếu con trỏ cha này là NUL / nil / none đối với bất kỳ nút nào, điều đó có nghĩa là nó chưa được phát hiện bởi BFS hoặc chính nút nguồn / nút gốc.
Shashank

@Shashank Nếu chúng tôi không duy trì khoảng cách thì làm sao chúng tôi biết khoảng cách ngắn nhất, vui lòng giải thích thêm.
Gaurav Sehgal

12
@gauravsehgal Nhận xét đó dành cho đồ thị có các cạnh không có trọng số. BFS sẽ tìm thấy khoảng cách ngắn nhất đơn giản chỉ vì mẫu tìm kiếm xuyên tâm của nó xem xét các nút theo thứ tự khoảng cách của chúng từ điểm bắt đầu.
Shashank

13
Một lời khuyên cho độc giả về nhận xét của @ Shashank: không trọng số và có trọng số đồng đều (ví dụ: tất cả các cạnh có trọng số = 5) là tương đương.
TWiStErRob

53

Như đã chỉ ra ở trên, BFS chỉ có thể được sử dụng để tìm đường đi ngắn nhất trong biểu đồ nếu:

  1. Không có vòng lặp

  2. Tất cả các cạnh có cùng trọng lượng hoặc không có trọng lượng.

Để tìm con đường ngắn nhất, tất cả những gì bạn phải làm là bắt đầu từ nguồn và thực hiện tìm kiếm đầu tiên và dừng lại khi bạn tìm thấy Node đích của mình. Điều bổ sung duy nhất bạn cần làm là có một mảng trước [n] sẽ lưu trữ nút trước đó cho mỗi nút được truy cập. Nguồn trước có thể là null.

Để in đường dẫn, lặp đơn giản qua mảng [] trước đó từ nguồn cho đến khi bạn đến đích và in các nút. DFS cũng có thể được sử dụng để tìm đường đi ngắn nhất trong biểu đồ trong các điều kiện tương tự.

Tuy nhiên, nếu đồ thị phức tạp hơn, chứa các cạnh và vòng có trọng số, thì chúng ta cần một phiên bản BFS phức tạp hơn, tức là thuật toán của Dijkstra.


1
Dijkstra nếu không có trọng lượng khác sử dụng bellman ford algo if -ve weight
shaunak1111

BFS có hoạt động để tìm tất cả các đường dẫn ngắn nhất giữa hai nút không?
Maria Ines Parnisari

35
@javaProgrammer, không đúng. BFS có thể được sử dụng để tìm đường đi ngắn nhất trong biểu đồ tuần hoàn không trọng số. Nếu một đồ thị không có trọng số, thì BFS có thể được áp dụng cho SP bất kể có vòng lặp.
Andrei Kaigorodov

3
To print the path, simple loop through the previous[] array from source till you reach destination.Nhưng trước đây của nguồn là null. Tôi nghĩ những gì bạn có nghĩa là , backtrace from destination using the previous array until you reach the source.
aandis

2
Tại sao bạn nói DFS sẽ hoạt động trong điều kiện tương tự? Có phải DFS không thể đi theo con đường vòng quanh ngây thơ để đi từ các nút bắt đầu-> kết thúc, và do đó cung cấp cho bạn một con đường không phải là ngắn nhất?
James Wierzba

26

Từ hướng dẫn ở đây

"Nó có một thuộc tính cực kỳ hữu ích là nếu tất cả các cạnh trong biểu đồ không có trọng số (hoặc cùng trọng số) thì lần đầu tiên một nút được truy cập là đường dẫn ngắn nhất đến nút đó từ nút nguồn"


Điều này tốt cho nút có thể truy cập trực tiếp (1-> 2) (2 được lấy trực tiếp từ 1). Đối với nút không thể truy cập trực tiếp, có nhiều công việc hơn (1-> 2-> 3, 3 không được tiếp cận trực tiếp từ 1). Tất nhiên, nó vẫn đúng khi xem xét riêng lẻ, tức là 1-> 2 & 2-> 3 riêng lẻ là những con đường ngắn nhất.
Manohar Reddy Poreddy

11


Cuối cùng tôi đã lãng phí 3 ngày để giải quyết một câu hỏi đồ thị
được sử dụng để
tìm khoảng cách ngắn nhất
bằng BFS

Muốn chia sẻ kinh nghiệm.

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

Tôi đã mất 3 ngày để thử tất cả các lựa chọn thay thế ở trên, xác minh và xác minh lại nhiều lần và
không phải là vấn đề.
(Cố gắng dành thời gian tìm kiếm các vấn đề khác, nếu bạn không tìm thấy bất kỳ vấn đề nào với trên 5).


Giải thích thêm từ bình luận dưới đây:

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

Giả sử ở trên là biểu đồ đồ thị của bạn
đi xuống
Đối với A, các phần tử là B & C
Đối với B, các phần tử là D & E
Đối với C, phần phụ là F & G

nói, nút bắt đầu là A

  1. khi bạn đạt A, đến, B & C khoảng cách ngắn nhất đến B & C từ A là 1

  2. khi bạn đạt D hoặc E, qua B, khoảng cách ngắn nhất đến A & D là 2 (A-> B-> D)

tương tự, A-> E là 2 (A-> B-> E)

Ngoài ra, A-> F & A-> G là 2

Vì vậy, bây giờ thay vì 1 khoảng cách giữa các nút, nếu là 6, thì chỉ cần nhân câu trả lời với 6
ví dụ,
nếu khoảng cách giữa mỗi nút là 1, thì A-> E là 2 (A-> B-> E = 1 + 1 )
nếu khoảng cách giữa mỗi lần là 6, thì A-> E là 12 (A-> B-> E = 6 + 6)

vâng, bfs có thể đi bất kỳ con đường nào
nhưng chúng tôi đang tính toán cho tất cả các đường dẫn

nếu bạn phải đi từ A đến Z, thì chúng ta sẽ đi tất cả các đường từ A đến trung gian I và vì sẽ có nhiều đường chúng ta loại bỏ tất cả trừ đường đi ngắn nhất cho đến I, sau đó tiếp tục với đường ngắn nhất tới nút J tiếp theo
nếu Có nhiều đường dẫn từ I đến J, chúng tôi chỉ lấy một
ví dụ ngắn nhất ,
giả sử,
A -> Tôi có khoảng cách 5
(BƯỚC) giả sử, tôi -> J chúng tôi có nhiều đường, trong khoảng cách 7 & 8, vì 7 là ngắn nhất
chúng tôi lấy A -> J là 5 (A-> Tôi ngắn nhất) + 8 (ngắn nhất bây giờ) = 13
vì vậy A-> J bây giờ là 13,
chúng tôi lặp lại ở trên (BƯỚC) cho J -> K và cứ thế, cho đến khi chúng tôi nhận được đến Z

Đọc phần này, 2 hoặc 3 lần, và vẽ trên giấy, bạn chắc chắn sẽ nhận được những gì tôi đang nói, chúc may mắn



Bạn có thể vui lòng giải thích làm thế nào bạn quản lý để tìm ra con đường ngắn nhất với tìm kiếm đầu tiên rộng. Một tìm kiếm đầu tiên chủ yếu tìm kiếm một nút, có thể có n đường dẫn đến một nút mục tiêu từ nút nguồn & bfs có thể đi theo bất kỳ đường dẫn nào. Làm thế nào để bạn xác định con đường tốt nhất?
thua kém

tôi đã thêm phần 'giải thích thêm' vào câu trả lời trên, cho tôi biết nếu điều đó thỏa mãn
Manohar Reddy Poreddy

1
Tôi thấy bạn đang cố chạy BFS trên biểu đồ có trọng số. Trong khoảng cách 7 & 8 tại sao bạn chọn 8? Tại sao không phải là 7? Điều gì xảy ra nếu nút 8 không có các cạnh tiếp theo? Dòng chảy sẽ phải chọn 7 rồi.
thua kém

câu hỏi hay, được nâng cấp, vâng, chúng tôi không vứt đi, chúng tôi theo dõi tất cả các nút liền kề, cho đến khi chúng tôi đến đích. BFS sẽ chỉ hoạt động khi chỉ có khoảng cách không đổi như tất cả 7 hoặc tất cả 8. Tôi đã đưa ra một cái chung có 7 & 8, còn được gọi là thuật toán của dijkstra.
Manohar Reddy Poreddy

xin lỗi, không chắc ý của bạn là gì, xem en.wikipedia.org/wiki/Dijkstra's_alacticm
Manohar Reddy Poreddy

2

Dựa trên câu trả lời acheron55 tôi đã đăng một triển khai có thể ở đây .
Đây là một tóm tắt ngắn gọn về nó:

Tất cả những gì bạn phải làm là theo dõi con đường mà mục tiêu đã đạt được. Một cách đơn giản để làm điều đó là đẩy vào Queuetoàn bộ đường dẫn được sử dụng để tiếp cận một nút, thay vì chính nút đó.
Lợi ích của việc này là khi đạt được mục tiêu, hàng đợi giữ đường dẫn được sử dụng để tiếp cận nó.
Điều này cũng có thể áp dụng cho các biểu đồ tuần hoàn, trong đó một nút có thể có nhiều hơn một cha.


0

Đến thăm chủ đề này sau một thời gian không hoạt động, nhưng cho rằng tôi không thấy câu trả lời thấu đáo, đây là hai xu của tôi.

Tìm kiếm đầu tiên sẽ luôn tìm thấy con đường ngắn nhất trong biểu đồ không trọng số. Đồ thị có thể theo chu kỳ hoặc theo chu kỳ.

Xem dưới đây cho mã giả. Mã giả này giả định rằng bạn đang sử dụng hàng đợi để thực hiện BFS. Nó cũng giả sử bạn có thể đánh dấu các đỉnh là đã truy cập và mỗi đỉnh lưu trữ một tham số khoảng cách, được khởi tạo thành vô cùng.

mark all vertices as unvisited
set the distance value of all vertices to infinity
set the distance value of the start vertex to 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 the value of x is the value of the end vertex: 
        return the distance value of x
    otherwise, 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
            enqueue them to the queue
if here: there is no path connecting the vertices

Lưu ý rằng phương pháp này không hoạt động đối với các biểu đồ có trọng số - vì điều đó, hãy xem thuật toán của Dijkstra.


-6

Các giải pháp sau đây hoạt động cho tất cả các trường hợp thử nghiệm.

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();
            }
        }
}

4
Downvote vì không trả lời câu hỏi. Chỉ cần dán đoạn mã sẽ không hoạt động trên SO.
Rishabh Agrahari
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.