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

时间:2011-12-05 00:35:00

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

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

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

enter image description here

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

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

8 个答案:

答案 0 :(得分:67)

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

Dijkstra's algorithm调整BFS,让您找到单源最短路径。

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

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

答案 1 :(得分:41)

如上所述,如果出现以下情况,BFS只能 用于查找图表中的最短路径:

  1. 没有循环

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

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

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

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

答案 2 :(得分:19)

来自tutorial here

  

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

答案 3 :(得分:8)

我浪费了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天尝试以上所有选择,验证&上面一次又一次地重新验证 他们不是问题。
(如果你发现上述任何问题,请尽量花时间寻找其他问题。)

更多解释来自以下评论:

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

以上是您的图表 图表下降
对于A,邻接是B& ç
对于B,邻接是D& Ë
对于C,邻接是F& G

说,起始节点是A

  1. 当你到达A,to,B& C到B&的最短距离A的C是1

  2. 当你到达D或E时,通过B,到A&的最短距离D为2(A-> B-> D)

  3. 类似地,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有多条路径,我们只需要最短的一条路 例如,
    假设,
    A - >我有距离5
    (STEP)假设,I - >我们有多条距离为7& 7的路径。 8,因为7是最短的 我们采取A - > J为5(A-> I最短)+8(现在最短)= 13
    所以A-> J现在是13
    我们现在在上面重复(STEP)J - > K等等,直到我们到达Z

    阅读这部分,2或3次,并在纸上画画,你一定会得到我说的,祝你好运

答案 4 :(得分:1)

根据acheron55 answer我发布了一个可能的实施here
以下是它的简要总结:

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

答案 5 :(得分:1)

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

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

有关伪代码,请参见下文。该伪代码假定您正在使用队列来实现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 :(得分:-1)

在以下经过同行评审的论文中,很好地解释了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可以更快。有时,E-BFS不能提供比传统BFS更好的优势,但是它从来没有变慢。

值得注意的是,尽管E-BFS简单易用,但似乎并未广为人知。

答案 7 :(得分:-4)

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

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