寻找最短路径时,广度优先搜索如何工作?


129

我已经进行了一些研究,但似乎缺少该算法的一小部分。我了解广度优先搜索的工作原理,但我不了解它如何将我带到特定的路径,而不是仅仅告诉我每个节点可以到达的位置。我想解释我的困惑的最简单方法是提供一个示例:

例如,假设我有一个这样的图形:

在此处输入图片说明

我的目标是从A到E(所有边均未加权)。

我从A开始,因为那是我的血统。我排队A,然后立即将A出队并对其进行探索。因为A连接到B和D,所以得到B和D。因此,我将B和D都排队。

我将B出队并进行探索,发现它导致了A(已经探索过)和C,因此我将C排队。然后出队D,发现它导致了我的目标E。然后我将C出队,并发现它也导致了我的目标E。

我从逻辑上知道最快的路径是A-> D-> E,但是我不确定广度优先搜索到底有多大帮助-我应该如何记录路径,以便在完成后可以分析结果并查看最短的路径是A-> D-> E?

另外,请注意,我实际上并没有使用树,因此没有“父”节点,只有子节点。


2
“另外,请注意,我实际上并没有使用树,因此没有“父”节点,只有子节点”-显然,您必须将父节点存储在某个地方。对于DFS,您是通过调用堆栈间接进行操作,对于BFS,则必须显式执行操作。我怕你无能为力:)
Voo

Answers:


85

从技术上讲,广度优先搜索(BFS)本身不能让您找到最短的路径,这仅仅是因为BFS不在寻找最短的路径:BFS描述了一种搜索图的策略,但是它并不表示您必须搜索特别是任何东西。

Dijkstra的算法 BFS适应性强,使您可以找到单源最短路径。

为了检索从原点到节点的最短路径,您需要为图中的每个节点维护两项:其当前最短距离和最短路径中的前一个节点。最初,所有距离都设置为无穷大,而所有前身设置为空。在您的示例中,将A的距离设置为零,然后继续使用BFS。在每个步骤上,您都检查是否可以改善后代的距离,即从原点到前辈的距离加上要探索的边的长度小于所讨论节点的当前最佳距离。如果可以改善距离,请设置新的最短路径,并记住获取该路径的前身。当BFS队列为空时,选择一个节点(在您的示例中为E),并将其前任遍历回原点。

如果这听起来有点令人困惑,则Wikipedia 在该主题上有一个不错的伪代码部分


谢谢!我读了前面的伪代码,但无法理解它,你的解释使它单击我
杰克

46
我想为以后看这篇文章的人做以下说明:如果未加权边缘,则无需为每个节点存储“当前最短距离”。所有需要存储的是发现的每个节点的父节点。因此,在检查节点并使所有后继节点排队时,只需将这些节点的父节点设置为要检查的节点(这将其标记为“ discovered”也将成为两倍)。如果此父节点指针为NUL / nil / None对于任何给定的节点,这意味着要么BFS尚未发现它,要么它是源/根节点本身。
Shashank

@Shashank如果我们不保持距离,那么我们怎么知道最短的距离,请进一步解释。
Gaurav Sehgal 2015年

12
@gauravsehgal该评论适用于未加权边的图形。BFS之所以会找到最短距离,仅仅是因为其径向搜索模式按节点到起点的距离顺序考虑节点。
Shashank 2015年

13
@Shashank评论的读者提示:未加权和均匀加权(例如,所有边的权重为5)是等效的。
TWiStErRob

53

如上所述,如果满足以下条件,则BFS 可用于查找图形中的最短路径:

  1. 没有循环

  2. 所有边缘的权重相同或不重。

要找到最短路径,您要做的就是从源头开始,先进行广度优先搜索,然后在找到目标节点时停止。您唯一需要做的事情就是拥有一个数组previous [n],该数组将为访问的每个节点存储前一个节点。source的前一个可以为null。

要打印路径,只需从源循环遍历previous []数组,直到到达目标并打印节点。DFS也可用于在相似条件下查找图形中的最短路径。

但是,如果图更复杂,包含加权的边和环,则我们需要更复杂的BFS版本,即Dijkstra的算法。


1
Dijkstra,如果没有-ve权重,则使用
贝尔曼福特

BFS是否可以找到两个节点之间的所有最短路径?
Maria Ines Parnisari 2014年

35
@javaProgrammer,这是不对的。BFS还可用于在非加权循环图中查找最短路径。如果图形未加权,则BFS可以应用于SP,而不管是否存在循环。
Andrei Kaigorodov,2015年

3
To print the path, simple loop through the previous[] array from source till you reach destination.但是源的前一个为null。我认为您的意思是backtrace from destination using the previous array until you reach the source
aandis '16

2
为什么说DFS将在类似条件下工作?DFS是否不可能采取幼稚的环回路线来从节点start-> end获得节点,从而为您提供一条并非最短的路径?
James Wierzba

26

这里的教程

“它具有极其有用的特性,如果图中的所有边均未加权(或权重相同),则第一次访问节点是从源节点到该节点的最短路径”


这对于直接可到达的节点(1-> 2)(从1直接到达2)很有用。对于非直接可达的节点,还有更多工作(1-> 2-> 3,不能直接从1达到3)。当然,单独考虑仍然是正确的,即1-> 2&2-> 3分别是最短路径。
Manohar Reddy Poreddy

11

我浪费了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时,从A到B&C的最短距离是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,并且由于会有很多路径,我们会丢弃除最短路径之外的所有路径,直到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。
弱者

好问题,已投票,是的,我们不会丢掉,我们会跟踪所有相邻节点,直到到达目的地。只有当像7或8那样的恒定距离时,BFS才起作用。我给出了一个具有7和8的通用距离,也称为dijkstra算法。
Manohar Reddy Poreddy

抱歉,不确定您的意思,请参阅en.wikipedia.org/wiki/Dijkstra's_algorithm
Manohar Reddy Poreddy

2

基于acheron55的答案,我在这里发布了一个可能的实现。
这是一个简短的总结:

您要做的就是跟踪达到目标的路径。一种简单的方法是推入Queue到达节点的整个路径,而不是节点本身。
这样做的好处是,到达目标后,队列将保留用于到达目标的路径。
这也适用于循环图,其中一个节点可以有多个父节点。


0

在一段时间不活动之后访问该线程,但是鉴于我没有找到完整的答案,这是我的两分钱。

广度优先搜索将始终在未加权图中找到最短路径。该图可以是循环的或非循环的。

参见下面的伪代码。该伪代码假定您正在使用队列来实现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
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

请注意,这种方法不适用于加权图-为此,请参见Dijkstra的算法。


-6

以下解决方案适用于所有测试用例。

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
因不回答这个问题而感到沮丧。仅仅粘贴代码片段对SO无效。
Rishabh Agrahari
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.