【问题标题】:How does a Breadth-First Search work when looking for Shortest Path?寻找最短路径时,广度优先搜索如何工作?
【发布时间】:2011-12-05 00:35:00
【问题描述】:

我做了一些研究,但我似乎遗漏了这个算法的一小部分。我了解广度优先搜索的工作原理,但我不明白它将如何将我带到特定路径,而不是仅仅告诉我每个单独的节点可以去哪里。我想解释我的困惑的最简单方法是提供一个例子:

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

我的目标是从 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?

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

【问题讨论】:

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

标签: java shortest-path breadth-first-search


【解决方案1】:

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

Dijkstra's algorithm 适配 BFS 让你找到单源最短路径。

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

如果这听起来有点令人困惑,维基百科有一个很好的 pseudocode section 主题。

【讨论】:

  • 我想为以后看这篇文章的人做以下说明:如果边缘未加权,则无需为每个节点存储“当前最短距离”。需要存储的只是发现的每个节点的父节点。因此,当您检查一个节点并将其所有后继节点排入队列时,只需将这些节点的父节点设置为您正在检查的节点(这将同时将它们标记为“已发现”)。如果此父指针为 NUL/nil/None对于任何给定节点,这意味着它尚未被 BFS 发现,或者它是源/根节点本身。
  • @Shashank 如果我们不保持距离那么我们怎么知道最短距离,请解释一下。
  • @gauravsehgal 该评论适用于边缘未加权的图。 BFS 会找到最短距离,这仅仅是因为它的径向搜索模式会按照节点与起点的距离顺序来考虑节点。
  • @Shashank 评论读者的提示:未加权和均匀加权(例如,所有边的权重=5)是等价的。
  • 可以证明,对于未加权的图,BFS 等价于 Dijkstra 算法,从而在这种情况下找到最短路径。
【解决方案2】:

如上所述,BFS可用于在以下情况下查找图中的最短路径:

  1. 没有循环

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

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

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

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

【讨论】:

  • Dijkstra 如果没有 -ve 权重,否则使用 bellman Ford 算法如果 -ve 权重
  • BFS 是否可以找到两个节点之间的所有最短路径?
  • @javaProgrammer,这是不对的。 BFS 也可用于在未加权循环图中找到最短路径。如果一个图没有加权,那么无论有没有循环,都可以对 SP 应用 BFS。
  • 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
  • 为什么说 DFS 会在类似的条件下工作? DFS 不可能采取天真的迂回路线从节点开始-> 结束,因此给你一个不是最短的路径吗?
【解决方案3】:

来自tutorial here

“它有一个非常有用的特性,如果一个图中的所有边都没有加权(或相同的权重),那么第一次访问一个节点是从源节点到该节点的最短路径”

【讨论】:

  • 这对直接可达节点(1->2)有好处(2直接从1到达)。对于不可直接到达的节点,有更多的工作(1->2->3,3 不是从 1 直接到达的)。当然,单独考虑仍然是正确的,即 1->2 和 2->3 单独是最短路径。
【解决方案4】:

我浪费了 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, to, B & C 时,从 A 到 B & C 的最短距离是 1

  2. 当您通过 B 到达 D 或 E 时,到 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
(步骤)假设,I -> J 我们有多条路径,距离为 7 和 8,因为 7 是最短的
我们取 A -> J 为 5(A->I 最短)+ 8(现在最短)= 13
所以 A->J 现在是 13
我们现在重复上面的(STEP)J -> K 等等,直到我们到达 Z

阅读这部分,2到3遍,然后在纸上画,你一定会明白我在说什么,祝你好运


【讨论】:

  • 您能否详细说明您是如何通过广度优先搜索找到最短路径的。广度优先搜索主要是搜索一个节点,从源节点到目标节点可以有n条路径,bfs可以走任意路径。您如何确定最佳路径?
  • 我在上面的答案中添加了“更多解释”部分,如果满足,请告诉我
  • 我看到您正在尝试在加权图上运行 BFS。在距离 7 和 8 中,您为什么选择 8?为什么不是7?如果 8 节点没有进一步的边缘到目的地怎么办?然后流程必须选择 7。
  • 好问题,赞成,是的,我们不会丢弃,我们会跟踪所有相邻节点,直到到达目的地。 BFS 仅在只有恒定距离(如全部 7 或全部 8)时才起作用。我给出了一个具有 7 和 8 的通用距离,也称为 dijkstra 算法。
  • 抱歉,不确定您的意思,请参阅en.wikipedia.org/wiki/Dijkstra's_algorithm
【解决方案5】:

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

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

请参阅下面的伪代码。此伪代码假定您正在使用队列来实现 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 (we’ll 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 算法。

【讨论】:

    【解决方案6】:

    基于acheron55 answer,我发布了一个可能的实现here
    这是一个简短的总结:

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

    【讨论】:

      【解决方案7】:

      以下经过同行评审的论文很好地解释了 BFS 如何计算最短路径,并附有我所知道的最有效的简单 BFS 算法以及工作代码:

      https://queue.acm.org/detail.cfm?id=3424304

      该论文解释了 BFS 如何计算由每个顶点的父指针表示的最短路径树,以及如何从父指针中恢复任意两个顶点之间的特定最短路径。 BFS 的解释有散文、伪代码和工作 C 程序三种形式。

      论文还描述了“高效 BFS”(E-BFS),这是经典教科书 BFS 的简单变体,可提高其效率。在渐近分析中,运行时间从 Theta(V+E) 提高到 Omega(V)。换句话说:经典 BFS 的运行时间总是与顶点数量加上边的数量成正比,而 E-BFS 有时运行的时间仅与顶点数量成正比,这可能要小得多。在实践中,E-BFS 可以快得多,具体取决于输入图。与经典 BFS 相比,E-BFS 有时没有任何优势,但也不会慢很多。

      值得注意的是,尽管 E-BFS 很简单,但它似乎并不广为人知。

      【讨论】:

      • 嗨,Terrence,欢迎来到堆栈溢出。该链接看起来很有趣,但您的答案本身似乎并没有试图回答这个问题。由于链接可能而且确实会中断,因此如果答案尝试包含链接资源中的相关详细信息,我们将不胜感激。
      【解决方案8】:

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

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

      【讨论】:

      • 因未回答问题而被否决。简单地粘贴代码 sn-p 在 SO 上不起作用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-06
      • 1970-01-01
      • 2018-12-25
      • 2013-01-13
      • 1970-01-01
      相关资源
      最近更新 更多