web-dev-qa-db-ja.com

深さ優先探索でパスをトレースして返す

だから、深さ優先検索を使用して解決したい問題があり、DFSが見つけた最初のパスを返します。これが私の(不完全な)DFS関数です。

    start = problem.getStartState()
    stack = Stack()
    visited = []
    stack.Push(start)
    if problem.isGoalState(problem.getStartState):
        return something
    while stack:
        parent = stack.pop()
        if parent in visited: continue
        if problem.isGoalState(parent):
            return something
        visited.append(parent)
        children = problem.getSuccessors(parent)
        for child in children:
            stack.Push(child[0])

StartState変数とgoalState変数は、単純にx、y座標のタプルです。問題は、さまざまなメソッドを持つクラスです。ここで重要なのはgetSuccessors(3項目タプルのリストの形式で特定の状態の子を返します。ただし、問題のこの部分では、タプルの最初の要素(child [0])のみです。子の状態をx、y座標で返します(重要です)およびisGoalState(目標状態のx、y座標を提供します)。

ですから、この時点でテストするのは難しいと思いますが、この関数は、他のすべてが適切に実装されていれば、目標状態に到達すると戻ります。何か足りない場合は教えてください。しかし、私の最大の問題は何を返すかです。最初から最後まで順番に、目標状態に到達するために必要なすべての状態のリストを出力するようにします。スタックには多くの未訪問の子が含まれるため、単にスタックを返すだけでうまくいくとは思えません。行き止まりに到達し、バックトラックしなければならないが、行き止まりのリストに行き止まりのタプルが残っている可能性があるため、私の訪問済みリストは有用なものにはなりません。希望するリストを取得するにはどうすればよいですか?

27
user1427661

あなたは正しい-あなたは単にスタックを返すことはできません、それは確かに多くの未訪問のノードが含まれています。

ただし、マップ(ディクショナリ)を維持することにより、map:Vertex->Vertex そのような parentMap[v] = the vertex we used to discover v、パスを取得できます。

必要な変更は、forループのほとんどです。

    for child in children:
        stack.Push(child[0])
        parentMap[child] = parent #this line was added

後で、ターゲットを見つけたときに、ソースからターゲットへのパス(擬似コード)を取得できます。

curr = target
while (curr != None):
  print curr
  curr = parentMap[curr]

順序が逆になることに注意してください。すべての要素をスタックにプッシュしてから印刷することで解決できます。

このスレッド でBFSの実際のパスを見つけることに関する同様の(同一のIMOではない)質問に一度答えました

別の解決策は、反復+スタックではなく再帰バージョンのDFSを使用することです。ターゲットが見つかったら、再帰内のすべてのcurrentノードをバックアップします。ただし、このソリューションではアルゴリズムを再帰に再設計する必要があります1。


追伸グラフに無限分岐が含まれている場合、DFSは(visitedセットを維持していても)ターゲットへのパスを見つけることができないことに注意してください。
完全なアルゴリズム(存在する場合は常にソリューションを検出)と最適なアルゴリズム(最短パスを検出)が必要な場合は、 [〜#〜] bfs [〜#〜] を使用できます。 =または 反復的な深化DFS またはさらに A *アルゴリズム ヒューリスティック関数がある場合

35
amit

問題に固有ではありませんが、このコードを微調整してさまざまなシナリオに適用できます。実際、スタックにもパスを保持させることができます。

例:

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


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 dfs_paths(graph, start, goal):
    stack = [(start, [start])]
    visited = set()
    while stack:
        (vertex, path) = stack.pop()
        if vertex not in visited:
            if vertex == goal:
                return path
            visited.add(vertex)
            for neighbor in graph[vertex]:
                stack.append((neighbor, path + [neighbor]))

print (dfs_paths(graph, 'A', 'F'))   #['A', 'B', 'E', 'F']
9
XueYu

このリンクはあなたを大いに助けるはずです...それはパスを返すDFS検索について広範に語る長い記事です...そして私は他の誰も投稿できる答えよりも良いと感じています

http://www.python.org/doc/essays/graphs/

4
Joran Beasley

PHPに似たものを実装しました。

背後にある基本的な考え方は次のとおりです。呼び出しスタックがある場合、別のスタックを維持する必要があるのはなぜですか。アルゴリズムが目標に到達したら、単に現在の呼び出しスタックに戻る必要があり、その結果、逆方向にとられたパスが読み取られます。変更されたアルゴリズムは次のとおりです。 return immediatelyセクションに注意してください。

/**
 * Depth-first path
 * 
 * @param Node $node        Currently evaluated node of the graph
 * @param Node $goal        The node we want to find
 *
 * @return The path as an array of Nodes, or false if there was no mach.
 */
function depthFirstPath (Node $node, Node $goal)
{
    // mark node as visited
    $node->visited = true;

    // If the goal is found, return immediately
    if ($node == $goal) {
        return array($node);
    }

    foreach ($node->outgoing as $Edge) {

        // We inspect the neighbours which are not yet visited
        if (!$Edge->outgoing->visited) {

            $result = $this->depthFirstPath($Edge->outgoing, $goal);

            // If the goal is found, return immediately
            if ($result) {
                // Insert the current node to the beginning of the result set
                array_unshift($result, $node);
                return $result;
            }
        }
    }

    return false;
}
1
user2555221