バックトラックと深さ優先検索の違いは何ですか?
バックトラックと深さ優先検索の違いは何ですか?
回答:
バックトラッキングは、より汎用的なアルゴリズムです。
深さ優先検索は、ツリー構造の検索に関連する特定の形式のバックトラックです。ウィキペディアから:
ルートから開始し(グラフの場合はルートとしてノードを選択)、バックトラックする前に各ブランチに沿って可能な限り探索します。
ツリーを操作する手段の一部としてバックトラックを使用しますが、ツリー構造に限定されます。
ただし、バックトラックは、ドメインの一部を削除できるあらゆるタイプの構造(論理ツリーであるかどうかに関係なく)で使用できます。Wikiの例では、チェス盤と特定の問題を使用しています。特定の動きを見て、それを排除し、次に次の可能な動きに戻ったり、排除したりすることができます。
別の関連する質問に対するこの回答は、より多くの洞察を提供すると思います。
私にとって、バックトラッキングとDFSの違いは、バックトラッキングは暗黙的なツリーを処理し、DFSは明示的なツリーを処理することです。これは些細なことのように見えますが、多くのことを意味します。問題のサーチスペースがバックトラックによってアクセスされると、暗黙的ツリーはその途中でトラバースおよびプルーニングされます。しかし、DFSの場合、それが扱うツリー/グラフは明示的に構築されており、検索が実行される前に、受け入れられないケースがすでに捨てられています(つまり、剪定されています)。
したがって、DFSはプルーニングなしでバックトラックするのに対し、バックトラックは暗黙的なツリーのDFSです。
バックトラッキングは通常、DFSと検索プルーニングとして実装されます。探索空間ツリーを縦方向にトラバースし、途中で部分解を構築します。ブルートフォースDFSは、実際には意味のない検索結果も含めて、すべての検索結果を作成できます。これも、すべてのソリューション(n!または2 ^ n)を構築するのに非常に非効率的です。したがって、実際には、DFSを実行するときに、実際のタスクのコンテキストでは意味をなさない部分的なソリューションも排除し、部分的なソリューションに焦点を当てる必要があります。これは実際のバックトラックテクニックです。部分的なソリューションをできるだけ早く破棄し、一歩下がって、局所最適を再度見つけようとします。
BFSを使用して検索スペースツリーをトラバースし、途中でバックトラッキング戦略を実行するために何も停止しませんが、実際には、検索状態をレイヤーごとにキューに格納する必要があり、ツリーの幅が指数関数的に高さまで増加するため、意味がありません。そのため、非常に多くのスペースを非常に早く浪費します。そのため、通常、DFSを使用してツリーを走査します。この場合、検索状態はスタック(呼び出しスタックまたは明示的な構造)に格納され、ツリーの高さを超えることはできません。
ドナルド・クヌースによると、それは同じです。ダンシングリンクアルゴリズムに関する彼の論文のリンクは次のとおりです。これは、Nクイーンや数独ソルバーなどの「非ツリー」問題を解決するために使用されます。
私見、ほとんどの答えは不正確であるか、検証するための参照がありません。そのため、非常に明確な説明をリファレンスと共有させてください。
まず、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版
で 深さ優先探索、あなたは、ツリーのルートから開始し、その後、その後、各ブランチに沿っ限り、あなたを探るバックトラック後続の各親ノードに、それの子をトラバース
バックトラックは、ゴールの終わりから開始し、徐々に後方に移動して徐々にソリューションを構築するための一般的な用語です。
アイデア-任意のポイントから開始し、目的のエンドポイントかどうかを確認します。そうであれば、解決策を見つけ、次の可能なすべての位置に進みます。さらに先に行けない場合は、前の位置に戻って、現在のマークが付いている他の選択肢を探しますパスは私たちをソリューションに導きません。
現在、バックトラッキングとDFSは、2つの異なる抽象データ型に適用される同じアイデアに付けられた2つの異なる名前です。
アイデアがマトリックスデータ構造に適用される場合、それをバックトラックと呼びます。
同じアイデアがツリーまたはグラフに適用される場合、それをDFSと呼びます。
ここでの決まり文句は、マトリックスをグラフに変換し、グラフをマトリックスに変換できるということです。それで、私たちは実際にアイデアを適用します。グラフ上ではDFSと呼び、行列上ではバックトラックと呼びます。
両方のアルゴリズムの考え方は同じです。
バックトラッキングは、特定の終了条件を使用した深さ優先検索です。
迷路を歩くことを検討してください。各ステップで決定を下す場合、その決定はコールスタック(深さ優先検索を実行する)への呼び出しです...最後に到達したら、パスを返すことができます。ただし、行き止まりに到達した場合は、特定の決定から復帰する必要があります。本質的には、コールスタックの関数から復帰します。
だから私がバックトラックを考えるとき、私は気にします
私はここでバックトラックに関する私のビデオでそれを説明します。
バックトラッキングコードの分析を以下に示します。このバックトラッキングコードでは、特定の合計または目標となるすべての組み合わせが必要です。したがって、私はコールスタックを呼び出す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)