バックトラックと深さ優先検索の違いは何ですか?


104

バックトラックと深さ優先検索の違いは何ですか?

回答:


98

バックトラッキングは、より汎用的なアルゴリズムです。

深さ優先検索は、ツリー構造の検索に関連する特定の形式のバックトラックです。ウィキペディアから:

ルートから開始し(グラフの場合はルートとしてノードを選択)、バックトラックする前に各ブランチに沿って可能な限り探索します。

ツリーを操作する手段の一部としてバックトラックを使用しますが、ツリー構造に限定されます。

ただし、バックトラックは、ドメインの一部を削除できるあらゆるタイプの構造(論理ツリーであるかどうかに関係なく)で使用できます。Wikiの例では、チェス盤と特定の問題を使用しています。特定の動きを見て、それを排除し、次に次の可能な動きに戻ったり、排除したりすることができます。


13
ここで古代の投稿に応答します。良い答えですが、チェス盤の問題もツリーとして表現できませんでしたか?:-)特定の駒のチェス盤上の任意の位置から、将来に向かって伸びる可能性のある動きのツリーはありませんか?私の一部は、バックトラッキングを使用できるすべてのケースのように感じ、ツリーとしてモデル化することもできますが、その直感が正しいかどうかはわかりません。
The111

4
@ The111:実際、あらゆる検索問題はツリーとして表すことができます-可能な部分解ごとにノードがあり、各ノードからこの状態で行われる可能性のある1つ以上の代替選択肢へのエッジがあります。lcnの答えは、バックトラックは通常、再帰中に生成された(通常は暗黙的な)検索ツリー上のDFSが真実に最も近いということです。
j_random_hacker 2013

5
@j_random_hackerつまり、DFSはツリー(またはより一般的にはグラフ)を探索する方法であり、バックトラックは問題を解決する方法です(プルーニングとともにDFSを使用します)。:-)
The111

紛らわしいことに、ウィキペディアはバックトラックを深さ優先検索アルゴリズムとして説明しており、明らかにこれらの2つの概念を融合しています。
アンダーソングリーン

29

別の関連する質問に対するこの回答は、より多くの洞察を提供すると思います。

私にとって、バックトラッキングとDFSの違いは、バックトラッキングは暗黙的なツリーを処理し、DFSは明示的なツリーを処理することです。これは些細なことのように見えますが、多くのことを意味します。問題のサーチスペースがバックトラックによってアクセスされると、暗黙的ツリーはその途中でトラバースおよびプルーニングされます。しかし、DFSの場合、それが扱うツリー/グラフは明示的に構築されており、検索が実行される前に、受け入れられないケースがすでに捨てられています(つまり、剪定されています)。

したがって、DFSはプルーニングなしでバックトラックするのに対し、バックトラックは暗黙的なツリーのDFSです。


私は、バックトラックを暗黙のツリーを処理するものと考えるのは紛らわしいと思います。これを最初に読んだとき、私は同意しましたが、深く掘り下げて、「暗黙のツリー」が実際に再帰ツリーであることに気付きました。問題に関するものは何でも、インクリメンタルな文字列構築プロセスをモデル化した再帰ツリーがあります。剪定に関しては、それは総ブルートフォースが実行される再帰ツリーに対して行われる剪定です...(継続される)
Gang Fang

(続き)たとえば、 "answer"文字列のすべての順列を出力し、3番目の文字が文字 "a"である必要があるとしましょう。再帰ツリーの最初の2レベルはO(n!)に従いますが、3番目のレベルでは、「a」が付加されているブランチを除くすべてのブランチが枝刈り(バックトラック)されます。
Gang Fang

6

バックトラッキングは通常、DFSと検索プルーニングとして実装されます。探索空間ツリーを縦方向にトラバースし、途中で部分解を構築します。ブルートフォースDFSは、実際には意味のない検索結果も含めて、すべての検索結果を作成できます。これも、すべてのソリューション(n!または2 ^ n)を構築するのに非常に非効率的です。したがって、実際には、DFSを実行するときに、実際のタスクのコンテキストでは意味をなさない部分的なソリューションも排除し、部分的なソリューションに焦点を当てる必要があります。これは実際のバックトラックテクニックです。部分的なソリューションをできるだけ早く破棄し、一歩下がって、局所最適を再度見つけようとします。

BFSを使用して検索スペースツリーをトラバースし、途中でバックトラッキング戦略を実行するために何も停止しませんが、実際には、検索状態をレイヤーごとにキューに格納する必要があり、ツリーの幅が指数関数的に高さまで増加するため、意味がありません。そのため、非常に多くのスペースを非常に早く浪費します。そのため、通常、DFSを使用してツリーを走査します。この場合、検索状態はスタック(呼び出しスタックまたは明示的な構造)に格納され、ツリーの高さを超えることはできません。


5

通常、深さ優先検索は、値を探す実際のグラフ/ツリー構造を反復する方法ですが、バックトラックは、解決策を探す問題空間を反復します。バックトラックは、ツリーに必ずしも関連しない、より一般的なアルゴリズムです。


5

おそらく、DFSは特別な形式のバックトラックです。バックトラッキングは、DFSの一般的な形式です。

DFSを一般的な問題に拡張すると、それをバックトラックと呼ぶことができます。ツリー/グラフ関連の問題にバックトラッキングを使用する場合、それをDFSと呼ぶことができます。

彼らはアルゴリズムの面で同じ考えを持っています。


DFSとバックトラッキングの関係は、実際には逆です。これを詳しく説明する私の応答を確認してください。
KGhatak


5

私見、ほとんどの答えは不正確であるか、検証するための参照がありません。そのため、非常に明確な説明をリファレンスと共有させてください

まず、DFSは一般的なグラフトラバーサル(および検索)アルゴリズムです。したがって、任意のグラフ(またはフォレスト)にも適用できます。ツリーは特別な種類のグラフであるため、DFSはツリーでも機能します。本質的には、それが木などにのみ機能するとは言わないでください。

[1]に基づくバックトラッキングは、主にスペース(メモリ)の節約に使用される特殊なDFSです。このような種類のグラフアルゴリズムでは、隣接リストの表現を使用し、反復パターンを使用してノードのすべての隣接ノード(ツリーの場合は直接の子)にアクセスするのに慣れているため、これから説明する区別は混乱するかもしれません。、私たちはしばしばget_all_immediate_neighborsの悪い実装を無視しますにより、基盤となるアルゴリズムのメモリ使用量に違いが生じる可能性があるがよくあります。

さらに、グラフノードに分岐係数bと直径h(ツリーの場合、これはツリーの高さ)がある場合、ノードを訪問する各ステップですべての隣接ノードを保存すると、メモリ要件はbig-O(bh)になります。ただし、一度に1つの(即時)ネイバーのみを取得して展開すると、メモリの複雑度はbig-O(h)に減少します。前者の実装はDFSと呼ばれていますが、後者はバックトラッキングと呼ばれています。ます。

さて、高級言語で作業している場合は、DFSを装ってバックトラックを実際に使用している可能性があります。さらに、非常に大きな問題セットの訪問済みノードを追跡することは、メモリを集中的に使用する可能性があります。get_all_immediate_neighborsの注意深い設計を要求する(または無限ループに入ることなくノードの再訪問を処理できるアルゴリズム)のます。

[1] Stuart RussellおよびPeter Norvig、人工知能:現代のアプローチ、第3版


2

深さ優先は、ツリーをトラバースまたは検索するためのアルゴリズムです。こちらをご覧ください。バックトラックとは、ソリューション候補が形成され、後で元の状態にバックトラックすることによって破棄される場合に使用される、はるかに広い用語です。こちらをご覧ください。深さ優先検索では、バックトラックを使用して最初にブランチ(ソリューション候補)を検索し、成功しない場合は他のブランチを検索します。


2

DFSは、グラフを探索またはトラバースする方法を記述します。それは選択を与えられて可能な限り深く行くという概念に焦点を当てています。

バックトラッキングは、通常DFSを介して実装されますが、約束のない検索サブスペースを可能な限り早期にプルーニングするという概念により重点を置いています。


1

深さ優先探索、あなたは、ツリーのルートから開始し、その後、その後、各ブランチに沿っ限り、あなたを探るバックトラック後続の各親ノードに、それの子をトラバース

バックトラックは、ゴールの終わりから開始し、徐々に後方に移動して徐々にソリューションを構築するための一般的な用語です。


4
バックトラックは、最後から開始して後方に移動することを意味しません。行き止まりが見つかった場合は、訪問したノードのログをバックトラックに保存します。
ギュンター・イエナ

1
「最後から…」、ハァッ!
7kemZmani 2017

1

IMO、バックトラッキングの特定のノードで、まず各子に分岐する深さを試してみますが、子ノードのいずれかに分岐する前に、前の子の状態を「ワイプアウト」する必要があります(この手順は戻ると同等です)親ノードに移動します)。言い換えれば、各兄弟の状態が互いに影響を与えるべきではありません。

逆に、通常のDFSアルゴリズムでは、通常、この制約はありません。次の兄弟ノードを構築するために以前の兄弟の状態を一掃(バックトラック)する必要はありません。


1

アイデア-任意のポイントから開始し、目的のエンドポイントかどうかを確認します。そうであれば、解決策を見つけ、次の可能なすべての位置に進みます。さらに先に行けない場合は、前の位置に戻って、現在のマークが付いている他の選択肢を探しますパスは私たちをソリューションに導きません。

現在、バックトラッキングとDFSは、2つの異なる抽象データ型に適用される同じアイデアに付けられた2つの異なる名前です。

アイデアがマトリックスデータ構造に適用される場合、それをバックトラックと呼びます。

同じアイデアがツリーまたはグラフに適用される場合、それをDFSと呼びます。

ここでの決まり文句は、マトリックスをグラフに変換し、グラフをマトリックスに変換できるということです。それで、私たちは実際にアイデアを適用します。グラフ上ではDFSと呼び、行列上ではバックトラックと呼びます。

両方のアルゴリズムの考え方は同じです。


0

バックトラッキングは、特定の終了条件を使用した深さ優先検索です。

迷路を歩くことを検討してください。各ステップで決定を下す場合、その決定はコールスタック(深さ優先検索を実行する)への呼び出しです...最後に到達したら、パスを返すことができます。ただし、行き止まりに到達した場合は、特定の決定から復帰する必要があります。本質的には、コールスタックの関数から復帰します。

だから私がバックトラックを考えるとき、私は気にします

  1. 状態
  2. 決定
  3. 基本ケース(終了条件)

私はここでバックトラックに関する私のビデオでそれを説明します

バックトラッキングコードの分析を以下に示します。このバックトラッキングコードでは、特定の合計または目標となるすべての組み合わせが必要です。したがって、私はコールスタックを呼び出す3つの決定を行います。各決定では、パスの一部として番号を選択してターゲット番号に到達するか、その番号をスキップするか、または選択して再度選択することができます。そして、終了条件に達した場合、バックトラックのステップはただ戻ることです。リターンは、コールスタック上のそのコールから抜け出すため、バックトラックのステップです。

class Solution:    

"""

Approach: Backtracking 

State
    -candidates 
    -index 
    -target 

Decisions
    -pick one --> call func changing state: index + 1, target - candidates[index], path + [candidates[index]]
    -pick one again --> call func changing state: index, target - candidates[index], path + [candidates[index]]
    -skip one --> call func changing state: index + 1, target, path

Base Cases (Termination Conditions)
    -if target == 0 and path not in ret
        append path to ret
    -if target < 0: 
        return # backtrack 

"""

def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
    """
    @desc find all unique combos summing to target
    @args
        @arg1 candidates, list of ints
        @arg2 target, an int
    @ret ret, list of lists 
    """
    if not candidates or min(candidates) > target: return []

    ret = []
    self.dfs(candidates, 0, target, [], ret)
    return ret 

def dfs(self, nums, index, target, path, ret):
    if target == 0 and path not in ret: 
        ret.append(path)
        return #backtracking 
    elif target < 0 or index >= len(nums): 
        return #backtracking 


    # for i in range(index, len(nums)): 
    #     self.dfs(nums, i, target-nums[i], path+[nums[i]], ret)

    pick_one = self.dfs(nums, index + 1, target - nums[index], path + [nums[index]], ret)
    pick_one_again = self.dfs(nums, index, target - nums[index], path + [nums[index]], ret)
    skip_one = self.dfs(nums, index + 1, target, path, ret)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.