「Pythonを使ったバックトラックの理解:初心者ガイド」
「初心者ガイド:Pythonでのバックトラックの理解方法」
バックトラッキングは、問題のすべての可能な解を探索するために使用されるアルゴリズムです。この技術では、解を段階的に構築し、正しくない場合やすべての可能性が探索された場合には、解を元に戻します。バックトラッキングは、1つ以上の正しい解の可能性がある問題によく使用されます。バックトラッキングは、再帰を使用して実装されることがよくあります。
バックトラッキングの種類:
バックトラッキングは、決定問題、最適化問題、列挙問題などの問題を解決するために使用できますが、バックトラッキングにはさまざまなタイプがあります。
- 再帰的バックトラッキング:バックトラッキングは、再帰関数を使用して実装されることがよくあり、各再帰呼び出しは1つの決定を表します。一部のケースでは、スタックとキューを使用してイテレーションバックトラッキングが使用されます。
- 最適化バックトラッキング:一部の問題では、バックトラッキングはすべての可能な解だけでなく、可能性の中から最適な解を見つけるためにも使用されます。これには、最適な解につながらない呼び出しを除外するための枝刈り技術が使用されます。時には、バックトラッキングは動的計画法でのメモ化やタブ化と組み合わせることもあります。
- ヒューリスティックバックトラッキング:この種のバックトラッキングは、主にAIで使用されます。完全な探索空間の場合、ヒューリスティックを使用してバックトラッキングアルゴリズムを最適な解に導くことができます。
アプローチ:
まず、上記の図に示すように状態ツリーを形成します。この状態ツリーには、与えられた問題を解決するためのさまざまな状態が含まれます。次に、各状態をトラバースして、それが解であるか解につながるかを特定し、その結果に応じてそのイテレーションで無効な解をすべて破棄するかどうかを決定します。
Pythonでバックトラッキングを行う方法をいくつかの例を通じて学びましょう:
- 「GeForce NOW-vemberは50以上の新しいゲームをクラウドでストリーミングする」
- ゲーミングでのファウンデーションモデルの使い方はどのようなものですか?
- AIとブロックチェーンの統合によるゲームセキュリティの拡張
1. 与えられた配列のすべての部分集合を見つける
一意の要素で構成された整数配列nums
が与えられた場合、すべての可能な部分集合(べき集合)を返します。
解の集合には、重複する部分集合が含まれていてはなりません。解は任意の順序で返す必要があります
例1:
Input: nums = [1,2,3]Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
例2:
Input: nums = [0]Output: [[],[0]]
コード:
class Solution: def subsets(self, nums: List[int]) -> List[List[int]]: result, subset, = [], [] result = self.retrieve_subsets(nums, result, subset, 0) return result def retrieve_subsets(self, nums, result, subset, index): result.append(subset[:]) for i in range(index, len(nums)): subset.append(nums[i]) self.retrieve_subsets(nums, result, subset, i+1) subset.pop() return result
説明:
* 初期配列を取ります - [1,2,3]* 初期状態 - 配列(nums)= [1,2,3] - result = [] - subset = [] - index = 0* 状態 - 1 [初期関数呼び出し] - 結果:result = [[]]* 状態 - 2 [インデックス=0の再帰呼び出し] - index = 0 - subset = [1] - 更新されたsubsetおよびindex = 1で再帰呼び出しが行われます* 状態 - 3 [インデックス=1の再帰呼び出し] - index = 1 - subset = [1,2] - 更新されたsubsetおよびindex = 2で再帰呼び出しが行われます* 状態 - 4 [インデックス=2の再帰呼び出し] - index = 2 - subset = [1,2,3] - 元のsubsetがないため、更新されたsubsetで再帰呼び出しが行われず、ループは終了します* 状態 - 5 [インデックス=1にバックトラッキング] - 直前の再帰呼び出しでインデックス=1にバックトラッキング - subsetから最後の要素を取り除きます、subset = [1,2] - ループは次のイテレーション、i = 2に進み、Arr[2]をsubsetに追加し、subset = [1, 2, 3]となります。* 状態 - 6 [インデックス=0にバックトラッキング] - 直前の再帰呼び出しでインデックス=1にバックトラッキング - subsetから最後の要素を取り除きます、subset = [1] - ループは次のイテレーション、i = 1に進み、Arr[1]をsubsetに追加し、subset = [1, 3]となり、 このプロセスは全ての可能な状態が探索されるまで続きます。結果:result = [[],[1],[1, 2],[1, 2, 3],[1, 3],[2],[2, 3],[3]]
2. ソースからターゲットまでのすべてのパス
ノード番号 0
からノード番号 n - 1
へのパスを見つけ、任意の順序でそれらを返します。
以下のようにグラフが与えられます: graph[i]
はノード i
から訪れることができるすべてのノードのリストです(すなわち、ノード i
からノード graph[i][j]
への有向エッジが存在します)。
例 1:
Input: graph = [[1,2],[3],[3],[]]Output: [[0,1,3],[0,2,3]]Explanation: パスは 2 つあります: 0 -> 1 -> 3 および 0 -> 2 -> 3。
例 2:
Input: graph = [[4,3,1],[3,2,4],[3],[4],[]]Output: [[0,4],[0,3,4],[0,1,3,4],[0,1,2,3,4],[0,1,4]]
コード:
class Solution: def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]: hash_map = {i:graph[i] for i in range(len(graph))} result, interm_res, current_node = [], [], 0 self.retrieve_path(hash_map, result, interm_res, current_node) return result def retrieve_path(self, hash_map, result, interm_res, current_node): if current_node == len(hash_map.keys()) - 1: result.append(interm_res + [current_node]) elif hash_map[current_node]: for i in hash_map[current_node]: self.retrieve_path(hash_map, result, interm_res+[current_node], i)
説明:
- allPathsSourceTarget メソッド: * 入力に、リストのリストの形式で与えられたグラフを取ります。 * ステップ 1 はノードへの方向をマッピングすることです。このため、ノードと方向情報を格納するための辞書を作成します。 * 結果を収集するためのリスト result、構築中の中間パスを格納する interm_res、現在のノードを追跡する current_node の 2 つのリストを初期化します。 * 最後に retrieve_path メソッドを呼び出します。このメソッドには上記の変数すべてが入力されます。- retrieve_path メソッド: * 再帰を使用してバックトラッキングを行い、すべての可能なパスを見つけます。 * 再帰には終了条件が必要であり、ここでは current_node がリストの最後の数と等しい場合は、パスが見つかったことを示し、iterm_list に current_node を追加し、result に追加します。 * current_node に接続がある場合、例えば 3 は接続がないが、-1 、「0,1,2」は接続がある場合、コードはそれらを探索して可能なパスを見つけるためにそれらを反復処理します。 * 各接続に対して、コードはメソッドへの再帰呼び出しを行い、更新されたパラメータを渡して接続がすべて探索されるまで続けます。
3. N-クイーン
n-クイーン パズルは、2 つのクイーンが互いに攻撃しないように、n x n
のチェスボード上に n
個のクイーンを配置する問題です。
整数 n
が与えられた場合、n-クイーン パズルの異なる解の数を返します。
例1:
入力:n = 4出力:2説明:4-Queensパズルには、以下のように2つの異なる解があります。
例2:
入力:n = 1出力:1
コード:
class Solution: def totalNQueens(self, n: int) -> int: def is_safe(board, current_queen, col): for i in range(current_queen): if board[i] == col or \ board[i] - i == col - current_queen or \ board[i] + i == col + current_queen: return False return True def solve(current_queen): if current_queen== n: nonlocal count count += 1 return for col in range(n): if is_safe(board, current_queen, col): board[current_queen] = col solve(current_queen+ 1) board = [-1] * n count = 0 solve(0) return count
説明:
* totalNQueensメソッドはN*NのチェスボードにおけるNクイーンを計算するために使用されます。
* is_safe関数はチェスボード上の特定の位置にクイーンを配置しても安全かどうかをチェックします。クイーンの直線部分と対角線を探索し、安全であればTrueを返します。
* solveは再帰関数で、すべてのクイーンをチェスボード上に配置します。
* もしcurrent_queenがnと等しい場合、全てのクイーンがボード上に配置されたことを意味し、結果を1増やします。
* そうでなければ、その行の各位置について再帰呼び出しを行い、クイーンを安全な位置に配置し、次の行の次のクイーンに進みます。
* すべての位置が成功裏に探索されるまでプロセスが続きます。
バックトラッキングの最適化と枝刈り
基本的な例をカバーしましたが、バックトラッキングを最適化する方法は多くあり、すべての可能な解を探索するのではなく、最適な解または適切な解を見つけるためにバックトラッキングを使用することもできます。早期終了や制約特定のプログラミングは、解が見つかった場合や特定の制約が満たされた場合にバックトラッキングを停止するために実装できます。
対称性の破壊、フォワードチェック、およびバウンディングなどのテクニックを使用して、無効な状態を排除し、状態の数を減らすことができます。さらにバックトラッキングと組み合わせてメモ化やタブ化などの動的プログラミングのアプローチを実装することもできます。
バックトラッキングの注意点
バックトラッキングは多くの場面で有用ですが、バックトラッキングを行う際に注意すべきいくつかの一般的な問題があります。
- 指数時間の計算量:多重再帰により、最悪の場合には指数時間の計算量になるため、大きな問題には実用的ではありません。
- メモリの消費:再帰的なバックトラッキングは再帰的な呼び出しスタックにより、かなりのメモリを消費することがあり、スタックオーバーフローやメモリを超えるエラーの原因となる可能性があります。
- 最適化のバランス:過度の最適化は解を見逃す原因となり、逆に最適化不足はアルゴリズムの遅延につながるため、最適な最適化を見つけることは難しいかもしれません。
利点もあれば、欠点もあることを忘れないでください。それが宇宙の構造ですから。
バックトラッキングの詳細な問題をもっと探索したい場合は、こちらのリンクを使用してください。約100の問題が含まれています。
途中で行き詰まった場合は、メールで連絡してください。お手伝いできることを嬉しく思います。楽しいプログラミングを。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles