web-dev-qa-db-ja.com

幅優先検索で最短パスノードを見つける

enter image description here

上記のグラフで幅優先検索を実行して、Node 0からNode 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アルゴを経由するノードを追跡する必要があります。 [0,3,2,5,6]のように、ノード6に到達するまでたどった。そのため、shortestPathという名前のリストを作成し、訪問したノードの以前のノードを保存して、ノードのリストを取得しようとしています。 参照

しかし、それは機能していないようです。最短パスは[0,3,2,5,6]です

リストにあるのはShortest path: [0, 0, 0, 0, 1, 3, 3, 2, 5]です

部分的には正しいですが、余分な1が表示されます。

shortestPathリストの最初の要素0から再び開始し、トラバースとバックトラックを開始します。 1には3へのエッジがないため、0から3から5にバックトラックして移動すると、答えは得られますが、それが正しい方法かどうか確認してください。

最短経路のノードを取得するための理想的な方法は何ですか?

6
underdog

訪問したすべてのノードを単一のリストに格納しても、最終的にはどのノードがターゲットノードにつながったノードであり、どのノードが行き止まりだったのかを知る方法がないため、最短経路を見つけるのに役立ちません。

あなたがする必要があるのはすべてのノードに対してであり、開始ノードからのパスに前のノードを格納します。

したがって、マップを作成しますMap<Integer, Integer> 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);
}
9
Anton

あなたが見ることができるように acheron55答え

「グラフのすべてのエッジが重み付けされていない(または同じ重みである)場合、最初にノードにアクセスしたときに、ソースノードからそのノードへの最短パスになるという非常に便利な特性があります。」

だからあなたがしなければならないすべては、目標が到達されたパスを追跡することです。これを行う簡単な方法は、ノード自体ではなく、ノードに到達するために使用されるパス全体を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;
}

これは、ノードが複数の親を持つことができる循環グラフにも適用できます。

3
c0der

User3290797によってすでに与えられた回答に加えて。

重み付けされていないグラフを扱っているようです。これをすべてのエッジの重みが1であると解釈します。この場合、ルートノードまでの距離をグラフのすべてのノードに関連付けると(幅優先トラバーサル)、任意の最短パスを再構築するのは簡単になります。ノード、さらには複数のノードがあるかどうかを検出します。

あなたがする必要があるのは、ターゲットノードから開始し、深さの値がちょうど1小さい近傍のみを考慮する、同じグラフの幅(すべての最短パスが必要な場合)または深さ優先のトラバースです。 same graph but with distances from node 0

したがって、距離4(ノード6)から3、2、1、0にジャンプする必要があります。ジャンプする方法は(この場合は)1つだけです。

ノード4への最短経路に関心がある場合、結果は距離2-1-0またはノード4-3-0または4-8-0になります。

ところで、このアプローチは、重み付けされたグラフ(非負の重み)でも機能するように簡単に変更できます。有効な近傍は、現在のエッジからエッジの重みを引いたものに等しい距離を持つものです。最短経路の方が良い場合があります。

2
Tymur Gubayev