【问题标题】:Finding the shortest path nodes with breadth first search使用广度优先搜索找到最短路径节点
【发布时间】:2017-06-06 23:37:00
【问题描述】:

我在上图中运行广度优先搜索以找到从Node 0Node 6 的最短路径。

我的代码

public List<Integer> shortestPathBFS(int startNode, int nodeToBeFound){
        boolean shortestPathFound = false;
        Queue<Integer> queue = new LinkedList<Integer>();
        Set<Integer> visitedNodes = new HashSet<Integer>();
        List<Integer> shortestPath = new ArrayList<Integer>();
        queue.add(startNode);
        shortestPath.add(startNode);

        while (!queue.isEmpty()) {
            int nextNode = queue.peek();
            shortestPathFound = (nextNode == nodeToBeFound) ? true : false;
            if(shortestPathFound)break;
            visitedNodes.add(nextNode);
            System.out.println(queue);
            Integer unvisitedNode = this.getUnvisitedNode(nextNode, visitedNodes);

            if (unvisitedNode != null) {
                    queue.add(unvisitedNode);
                    visitedNodes.add(unvisitedNode);
                    shortestPath.add(nextNode); //Adding the previous node of the visited node 
                    shortestPathFound = (unvisitedNode == nodeToBeFound) ? true : false;
                    if(shortestPathFound)break;
                } else {
                    queue.poll();
                }
        }
        return shortestPath;
    }

我需要追踪 BFS 算法通过的节点。遍历到节点 6,如[0,3,2,5,6]。为此,我创建了一个名为 shortestPath 的列表并尝试存储访问节点的先前节点,以获取节点列表。 Referred

但它似乎不起作用。最短路径为[0,3,2,5,6]

在列表中我得到的是Shortest path: [0, 0, 0, 0, 1, 3, 3, 2, 5]

它部分正确,但给出了额外的 1

如果我再次从shortestPath 列表的第一个元素0 开始并开始遍历和回溯。就像13 没有优势,所以我回溯并从0 移动到35,我会得到答案,但不确定这是否是正确的方法。

获取最短路径的节点的理想方法是什么?

【问题讨论】:

  • 在此处查看第二个答案:stackoverflow.com/questions/8379785/…
  • 第二个答案解释了如何在加权图上运行 BFS
  • 它说:所有边的权重相同或没有权重。您可以假设所有边的权重都相同
  • 是的,假设所有边的权重相同。现在你将如何获得节点?一个节点 x 还有 3 个具有相同权重的节点。你会穿越哪一个?你怎么知道哪个是最好的。此外,我的问题与答案试图说的完全不同。请再次阅读我的问题。我不是在寻找最短路径,BFS 默认会寻找,我正在寻找打印最短路径的节点。收到了吗?
  • 每次访问子节点时保存父节点。假设您从 0 开始。BFS 可能首先访问 8、3 和 1。您将它们的父级保存为 0。然后您访问例如 4、2 和 7。它们的父级将是 8、3 和 1,依此类推。当您到达目标时,您将父母迭代回源。

标签: java algorithm data-structures breadth-first-search


【解决方案1】:

将所有访问过的节点存储在一个列表中对于找到最短路径没有帮助,因为最终您无法知道哪些节点是通向目标节点的节点,哪些是死胡同。

您需要做的是为每个节点将前一个节点存储在从起始节点开始的路径中。

所以,创建一个地图Map&lt;Integer, Integer&gt; parentNodes,而不是这个:

shortestPath.add(nextNode);

这样做:

parentNodes.put(unvisitedNode, nextNode);

到达目标节点后,您可以遍历该地图以找到返回起始节点的路径:

if(shortestPathFound) {
    List<Integer> shortestPath = new ArrayList<>();
    Integer node = nodeToBeFound;
    while(node != null) {
        shortestPath.add(node)
        node = parentNodes.get(node);
    }
    Collections.reverse(shortestPath);
}

【讨论】:

  • 如果图不是树(有循环),这可能不起作用
  • @c0der 该图可能包含循环,但最短路径不会。我们通过将每个已处理的节点存储在visitedNodes 中并仅将未访问的节点放入队列来确保没有节点被访问两次。
【解决方案2】:

除了 user3290797 已经给出的答案。

看起来您正在处理未加权的图表。我们将此解释为每条边的权重为 1。在这种情况下,一旦您将到根节点的距离与图的每个节点相关联(广度优先遍历),从任何节点,甚至检测是否有多个节点。

您需要做的只是从目标节点开始对同一图进行广度(如果您想要每条最短路径)或深度优先遍历,并且只考虑深度值恰好小于 1 的邻居。

所以我们需要从距离 4(节点 6)跳转到 3、2、1、0,并且只有一种方法(在这种情况下)这样做。

如果我们对到节点 4 的最短路径感兴趣,结果将是距离 2-1-0 或节点 4-3-0 或 4-8-0。

顺便说一句,这种方法也可以很容易地修改为使用加权图(具有非负权重):有效邻居是距离等于当前减去边缘权重的那些 - 这涉及一些实际计算并直接存储沿最短路径的先前节点可能会更好。

【讨论】:

  • 对于加权图,这种方法效果不佳,因为在某些情况下,如果新路径较短,您将不得不访问已标记的顶点。
  • 不,唯一的区别是哪些邻居节点被认为是好的(加权只是更一般的情况)。对于当前节点N 和与根R 的当前距离为d(R, N),节点M 位于从RN 的最短路径上,且仅当夫d(R, N) = d(R, M) + d(M, N)
【解决方案3】:

正如您在acheron55 answer 中看到的:

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

因此,您所要做的就是跟踪到达目标的路径。 一种简单的方法是将用于到达节点的整个路径推入Queue,而不是节点本身。
这样做的好处是,当到达目标时,队列会保存用于到达它的路径。
这是一个简单的实现:

/**
 * unlike common bfs implementation queue does not hold a nodes, but rather collections
 * of nodes. each collection represents the path through which a certain node has
 * been reached, the node being the last element in that collection
 */
private Queue<List<Node>> queue;

//a collection of visited nodes
private Set<Node> visited;

public boolean bfs(Node node) {

    if(node == null){ return false; }

    queue = new LinkedList<>(); //initialize queue
    visited = new HashSet<>();  //initialize visited log

    //a collection to hold the path through which a node has been reached
    //the node it self is the last element in that collection
    List<Node> pathToNode = new ArrayList<>();
    pathToNode.add(node);

    queue.add(pathToNode);

    while (! queue.isEmpty()) {

        pathToNode = queue.poll();
        //get node (last element) from queue
        node = pathToNode.get(pathToNode.size()-1);

        if(isSolved(node)) {
            //print path 
            System.out.println(pathToNode);
            return true;
        }

        //loop over neighbors
        for(Node nextNode : getNeighbors(node)){

            if(! isVisited(nextNode)) {
                //create a new collection representing the path to nextNode
                List<Node> pathToNextNode = new ArrayList<>(pathToNode);
                pathToNextNode.add(nextNode);
                queue.add(pathToNextNode); //add collection to the queue
            }
        }
    }

    return false;
}

private List<Node> getNeighbors(Node node) {/* TODO implement*/ return null;}

private boolean isSolved(Node node) {/* TODO implement*/ return false;}

private boolean isVisited(Node node) {
    if(visited.contains(node)) { return true;}
    visited.add(node);
    return false;
}

这也适用于循环图,其中一个节点可以有多个父节点。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-25
    • 1970-01-01
    • 1970-01-01
    • 2013-01-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多