幅優先検索でパスをトレースする方法は?


104

次の例のように、幅優先検索のパスをどのように追跡しますか。

keyを検索する場合は11、1から11を結ぶ最短のリストを返します。

[1, 4, 7, 11]

6
ケビンベーコン法に基づいて、数か月前に友人を助けていた古い割り当てでした。私の最終的な解決策は非常にずさんでした、私は基本的に別の幅優先検索を「巻き戻し」てバックトラックしました。私はもっ​​と良い解決策を見つけたくありません。
クリストファーマルキエタ2012年

21
優れた。エンジニアの立派な特性であるより良い答えを見つけるために、古い問題を再検討することを検討します。私はあなたの研究とキャリアの中であなたがよく願っています。
Peter Rowell、2012年

1
ほんとうにほんとうにほんとうに覚えないと、また同じ問題に出くわすと思います。
クリストファーマルキエタ2012年

回答:


193

最初にhttp://en.wikipedia.org/wiki/Breadth-first_searchを確認する必要があります。


以下は、パスのキューを表すためにリストのリストを使用した簡単な実装です。

# graph is in adjacent list representation
graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, start, end):
    # maintain a queue of paths
    queue = []
    # push the first path into the queue
    queue.append([start])
    while queue:
        # get the first path from the queue
        path = queue.pop(0)
        # get the last node from the path
        node = path[-1]
        # path found
        if node == end:
            return path
        # enumerate all adjacent nodes, construct a new path and push it into the queue
        for adjacent in graph.get(node, []):
            new_path = list(path)
            new_path.append(adjacent)
            queue.append(new_path)

print bfs(graph, '1', '11')

別のアプローチは、各ノードからその親へのマッピングを維持し、隣接ノードを検査するときに、その親を記録することです。検索が完了したら、親マッピングに従ってバックトレースするだけです。

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def backtrace(parent, start, end):
    path = [end]
    while path[-1] != start:
        path.append(parent[path[-1]])
    path.reverse()
    return path


def bfs(graph, start, end):
    parent = {}
    queue = []
    queue.append(start)
    while queue:
        node = queue.pop(0)
        if node == end:
            return backtrace(parent, start, end)
        for adjacent in graph.get(node, []):
            if node not in queue :
                parent[adjacent] = node # <<<<< record its parent 
                queue.append(adjacent)

print bfs(graph, '1', '11')

上記のコードは、循環がないという前提に基づいています。


2
これは素晴らしい!私の思考プロセスは、ある種のテーブルまたはマトリックスを作成することを信じるように導いてくれます、私はまだグラフについて学んでいません。ありがとうございました。
クリストファーマルキエタ2012年

私はまた、バックトレーシングアプローチを使用してみましたが、これはかなりすっきりしているようです。開始と終了のみを知っていて、その間のノードがどれもわからない場合、グラフを作成することは可能でしょうか?または、グラフ以外の別のアプローチですか?
クリストファーマルキエタ2012年

@ChristopherM私はあなたの質問を理解できませんでした:(
qiao

1
最初のアルゴリズムを適合させて、1から11までのすべてのパスを返すようにすることはできますか(複数あると想定)。
マリアイネスパルニサリ2014年

1
リストの代わりにcollections.dequeを使用することをお勧めします。list.pop(0)の複雑度はO(n)ですが、deque.popleft()はO(1)です
Omar_0x80

23

私はチャオの最初の答えがとても気に入りました!ここで欠けているのは、頂点を訪問済みとしてマークすることだけです。

なぜそれをする必要があるのですか?
ノード11から接続されている別のノード番号13があるとしましょう。ここでの目的はノード13を見つけることです。
少し実行すると、キューは次のようになります。

[[1, 2, 6], [1, 3, 10], [1, 4, 7], [1, 4, 8], [1, 2, 5, 9], [1, 2, 5, 10]]

末尾にノード番号10の2つのパスがあることに注意してください。
つまり、ノード番号10からのパスが2回チェックされます。この場合、ノード番号10には子がないため、それほど悪くはありません。しかし、それは本当に悪い可能性があります(ここでも、理由なしにそのノードを2回チェックします。)
ノード番号13は含まれていません。これらのパスは、プログラムが最後にノード番号10の2番目のパスに到達する前に戻らないようにします。そして、それを再チェックします。

足りないのは、訪れたノードをマークし、再度チェックしないようにするためのセットです。
これは、変更後のqiaoのコードです。

graph = {
    1: [2, 3, 4],
    2: [5, 6],
    3: [10],
    4: [7, 8],
    5: [9, 10],
    7: [11, 12],
    11: [13]
}


def bfs(graph_to_search, start, end):
    queue = [[start]]
    visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

プログラムの出力は次のようになります。

[1, 4, 7, 11, 13]

不必要なことをせずに再チェック..


6
as list.pop(0)がメモリの移動を引き起こすcollections.dequeために使用すると便利な場合があります。また、後世のために、DFSを実行したい場合は、変数を実際にのように設定します。queueO(n)path = queue.pop()queuestack
Sudhi 2016

11

とても簡単なコード。ノードを検出するたびにパスを追加し続けます。

graph = {
         'A': set(['B', 'C']),
         'B': set(['A', 'D', 'E']),
         'C': set(['A', 'F']),
         'D': set(['B']),
         'E': set(['B', 'F']),
         'F': set(['C', 'E'])
         }
def retunShortestPath(graph, start, end):

    queue = [(start,[start])]
    visited = set()

    while queue:
        vertex, path = queue.pop(0)
        visited.add(vertex)
        for node in graph[vertex]:
            if node == end:
                return path + [end]
            else:
                if node not in visited:
                    visited.add(node)
                    queue.append((node, path + [node]))

2
他の回答と比較して、あなたのコードはとても読みやすいと思います。どうもありがとうございました!
ミトコルセフ2018

8

私はこれを楽しみのためにコード化しようと思いました:

graph = {
        '1': ['2', '3', '4'],
        '2': ['5', '6'],
        '5': ['9', '10'],
        '4': ['7', '8'],
        '7': ['11', '12']
        }

def bfs(graph, forefront, end):
    # assumes no cycles

    next_forefront = [(node, path + ',' + node) for i, path in forefront if i in graph for node in graph[i]]

    for node,path in next_forefront:
        if node==end:
            return path
    else:
        return bfs(graph,next_forefront,end)

print bfs(graph,[('1','1')],'11')

# >>>
# 1, 4, 7, 11

サイクルが必要な場合は、これを追加できます。

for i, j in for_front: # allow cycles, add this code
    if i in graph:
        del graph[i]

next_for_frontをビルドした後。質問の続き、グラフにループが含まれている場合はどうなりますか?たとえば、ノード1がそれ自体に接続するエッジを持っている場合はどうでしょうか。グラフの2つのノード間に複数のエッジがある場合はどうなりますか?
ロバートキング

1

@Qiaoの最初の回答と@Orの追加の両方が好きです。処理を少し減らすために、Orの答えに追加したいと思います。

@Orの回答では、訪問したノードを追跡することは素晴らしいことです。また、プログラムを現在よりも早く終了させることもできます。forループのある時点ではであるcurrent_neighbour必要があり、endそれが発生すると最短経路が見つかり、プログラムが戻ることができます。

メソッドを次のように変更し、forループに細心の注意を払います

graph = {
1: [2, 3, 4],
2: [5, 6],
3: [10],
4: [7, 8],
5: [9, 10],
7: [11, 12],
11: [13]
}


    def bfs(graph_to_search, start, end):
        queue = [[start]]
        visited = set()

    while queue:
        # Gets the first path in the queue
        path = queue.pop(0)

        # Gets the last node in the path
        vertex = path[-1]

        # Checks if we got to the end
        if vertex == end:
            return path
        # We check if the current node is already in the visited nodes set in order not to recheck it
        elif vertex not in visited:
            # enumerate all adjacent nodes, construct a new path and push it into the queue
            for current_neighbour in graph_to_search.get(vertex, []):
                new_path = list(path)
                new_path.append(current_neighbour)
                queue.append(new_path)

                #No need to visit other neighbour. Return at once
                if current_neighbour == end
                    return new_path;

            # Mark the vertex as visited
            visited.add(vertex)


print bfs(graph, 1, 13)

出力と他のすべては同じになります。ただし、コードの処理時間は短くなります。これは、大きなグラフで特に役立ちます。これが将来の誰かの助けになることを願っています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.