最短経路を探す場合、幅優先検索はどのように機能しますか?


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

回答:


85

技術的には、幅優先検索(BFS)だけでは、最短パスを見つけることができません。これは、単にBFSが最短パスを探していないためです。BFSはグラフを検索するための戦略を説明しますが、検索する必要があるとは言いません特に何でも。

ダイクストラのアルゴリズムはBFSを適応させ、単一ソースの最短経路を見つけられるようにします。

起点からノードへの最短経路を取得するには、グラフ内の各ノードについて、現在の最短距離と最短経路の前のノードの2つの項目を維持する必要があります。最初はすべての距離が無限大に設定されており、すべての距離は空に設定されています。この例では、Aの距離をゼロに設定してから、BFSを続行します。各ステップで、子孫の距離を改善できるかどうかを確認します。つまり、起点から先行ノードまでの距離に、探索しているエッジの長さを加えたものが、問題のノードの現在の最適距離よりも短いかどうかを確認します。距離を改善できる場合は、新しい最短経路を設定し、その経路が取得された前の経路を覚えておいてください。BFSキューが空の場合、ノード(例では、E)を選択し、その先行ノードを元に戻します。

これが少し紛らわしく聞こえる場合は、ウィキペディアのトピックに素敵な疑似コードセクションがあります。


ありがとうございました!以前に疑似コードを読みましたが、それを理解できませんでした。あなたの説明が私にクリックしてくれました
Jake

46
将来この投稿を見る人のために、次のように書きたいと思います。エッジに重みが付けられていない場合、各ノードの「現在の最短距離」を保存する必要はありません。保存する必要があるのは、検出された各ノードの親です。したがって、ノードを調べてそのすべての後継者をキューに入れるときは、それらのノードの親を調べているノードに設定するだけです(これにより、ノードが「検出済み」とマークされます)。この親ポインターがNUL / nil / Noneの場合特定のノードの場合、これはBFSによってまだ検出されていないか、ソース/ルートノード自体であることを意味します。
Shashank 2013

@Shashank距離を維持しない場合、最短距離をどのようにして知ることができるでしょうか、詳しく説明してください。
Gaurav Sehgal

12
@gauravsehgalこのコメントは、重み付けされていないエッジを持つグラフに対するものです。BFSは、開始点からの距離順にノードを検討するその放射状検索パターンのために、最短距離を見つけます。
Shashank 2015

13
@Shashankのコメントの読者へのヒント:重み付けなしと均一に重み付けされている(たとえば、すべてのエッジにweight = 5がある)ことは同等です。
TWiStErRob 2015年

53

上で指摘したように、BFSは、次の場合にのみグラフ内の最短経路を見つけるために使用できます。

  1. ループはありません

  2. すべてのエッジの重みは同じか、まったくありません。

最短経路を見つけるには、ソースから開始して幅優先検索を実行し、宛先ノードが見つかったら停止するだけです。あなたがしなければならない唯一の追加のことは、訪問したすべてのノードの前のノードを格納する配列previous [n]を持つことです。ソースの前をnullにすることができます。

パスを出力するには、宛先から到達するまでソースから以前の[]配列を単純にループしてノードを出力します。DFSは、同様の条件下でグラフ内の最短経路を見つけるためにも使用できます。

ただし、グラフがより複雑で、重み付けされたエッジとループが含まれている場合は、より洗練されたバージョンのBFS、つまりダイクストラのアルゴリズムが必要です。


1
-ve重み場合アルゴなし- veの他の重み使用ベルマンフォード場合ダイクストラ
shaunak1111

BFSは2つのノード間のすべての最短パスを見つけるために機能しますか?
マリアイネスパルニサリ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

2
DFSが同様の条件下で機能するのはなぜですか。DFSが単純な迂回ルートをたどってノードの開始->終了から取得できないため、最短ではないパスを取得できませんか?
James Wierzba

26

ここにチュートリアル

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


これは直接到達可能なノード(1-> 2)に適しています(2は1から直接到達します)。直接到達できないノードの場合、作業が増えます(1-> 2-> 3、3は1から直接到達しません)。もちろん、個別に検討することは依然として真です。つまり、1-> 2&2-> 3は個別に最短パスです。
Manohar Reddy Poreddy

11

BFSを使用して 最短距離を見つける ために使用される
グラフの質問を最終的に解決した3日間を無駄にしました


体験を共有したい。

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

上記の方法をすべて試し、何度も確認と再確認を繰り返したところ
、問題はありませんでした。
(他の問題を探すのに時間を費やすようにしてください。もし上記の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への複数のパスがあり、我々は、最短1つの取り
、例を
、仮定
Aを- >私たちは距離5持っている
、と仮定する(ステップ)I - > J 7が最短であるので、我々は、距離7&8の複数のパスを持っています
A-> Jを5(A-> I最短)+ 8(現在は最短)= 13と
みなし、A-> Jが13
になるように、J(> ST)でJ-> Kを繰り返します。 Zへ

この部分を2、3回読んで、紙に描くと、きっと私が言っていることがわかります。



幅優先検索で最短経路をどのようにして見つけたのか、詳しく説明してください。幅優先検索は主にノードを検索します。ソースノードからゴールノードへのパスはn個ある可能性があり、bfsは任意のパスを使用できます。どのようにして最適な経路を決定していますか?
弱者

上記の回答に「詳細な説明」の部分を追加しました。それが満たされる場合はお知らせください
Manohar Reddy Poreddy

1
加重グラフでBFSを実行しようとしているようです。距離7と8のうち、なぜ8を選択したのですか?なぜ7ではないのですか?8ノードに宛先へのエッジがない場合はどうなりますか?次に、フローは7を選択する必要があります。
弱者

良い質問、賛成、はい、私たちは捨てません。目的地に到着するまで、すべての隣接ノードを追跡します。BFSは、7つすべてまたは8つすべてのような一定の距離しかない場合にのみ機能します。私は、ダイクストラのアルゴリズムとも呼ばれる7と8を持つ一般的な距離を与えました。
Manohar Reddy Poreddy 2017年

申し訳ありませんが、あなたが何を意味するか、見えないen.wikipedia.org/wiki/Dijkstra's_algorithm
ManoharさんレディPoreddy

2

acheron55の回答に基づいて、可能な実装をここに投稿しました
以下はその概要です。

あなたがしなければならないすべては、目標に到達したパスを追跡することです。これを行う簡単な方法Queueは、ノード自体ではなく、ノードに到達するために使用されるパス全体をプッシュすることです。
そうすることの利点は、ターゲットに到達したときに、キューに到達するために使用されたパスがキューに保持されることです。
これは、ノードが複数の親を持つことができる循環グラフにも適用できます。


0

非アクティブな状態が一定期間続いた後、このスレッドにアクセスしましたが、完全な答えが見当たらないため、ここに2セントを示します。

幅優先検索では、重み付けされていないグラフで常に最短経路が検索されます。グラフは循環式でも非循環式でもかまいません。

疑似コードについては、以下を参照してください。この疑似コードは、キューを使用して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

このアプローチは加重グラフでは機能しないことに注意してください。そのためには、ダイクストラのアルゴリズムを参照してください。


-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では機能しません。
リシャブアグラハリ2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.