剪定
複雑なバックトラッキング問題には 1 つ以上の制約が含まれることが多く、制約は「枝刈り」に使用されることがよくあります。
!!! 質問「質問例 3」
在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。
上記の制約を満たすために、プルーニング操作を追加する必要があります。検索プロセス中に、値が3 3の場合、3ノード、早めに戻って探索を停止します。
=== 「パイソン」
```python title="preorder_traversal_iii_compact.py"
[class]{}-[func]{pre_order}
```
=== 「C++」
```cpp title="preorder_traversal_iii_compact.cpp"
[class]{}-[func]{preOrder}
```
=== 「ジャワ」
```java title="preorder_traversal_iii_compact.java"
[class]{preorder_traversal_iii_compact}-[func]{preOrder}
```
=== 「C#」
```csharp title="preorder_traversal_iii_compact.cs"
[class]{preorder_traversal_iii_compact}-[func]{preOrder}
```
=== 「行きます」
```go title="preorder_traversal_iii_compact.go"
[class]{}-[func]{preOrderIII}
```
=== 「スウィフト」
```swift title="preorder_traversal_iii_compact.swift"
[class]{}-[func]{preOrder}
```
=== 「JS」
```javascript title="preorder_traversal_iii_compact.js"
[class]{}-[func]{preOrder}
```
=== 「TS」
```typescript title="preorder_traversal_iii_compact.ts"
[class]{}-[func]{preOrder}
```
=== 「ダーツ」
```dart title="preorder_traversal_iii_compact.dart"
[class]{}-[func]{preOrder}
```
=== 「錆」
```rust title="preorder_traversal_iii_compact.rs"
[class]{}-[func]{pre_order}
```
=== 「C」
```c title="preorder_traversal_iii_compact.c"
[class]{}-[func]{preOrder}
```
=== 「ジグ」
```zig title="preorder_traversal_iii_compact.zig"
[class]{}-[func]{preOrder}
```
剪定は非常に鮮やかな用語です。以下の図に示すように、検索プロセス中に、制約を満たさない検索分岐を「切断」して、多くの無意味な試行を回避し、検索効率を向上させます。
フレームワークコード
次に、コードの汎用性を向上させるために、バックトラッキングの「試行、ロールバック、プルーン」という主要なフレームワークを抽出してみます。
次のフレーム コードでは、state
問題の現在のステータスと、choices
現在のステータスで実行できる選択肢を表しています。
=== 「パイソン」
```python title=""
def backtrack(state: State, choices: list[choice], res: list[state]):
"""回溯算法框架"""
# 判断是否为解
if is_solution(state):
# 记录解
record_solution(state, res)
# 停止继续搜索
return
# 遍历所有选择
for choice in choices:
# 剪枝:判断选择是否合法
if is_valid(state, choice):
# 尝试:做出选择,更新状态
make_choice(state, choice)
backtrack(state, choices, res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
```
=== 「C++」
```cpp title=""
/* 回溯算法框架 */
void backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
for (Choice choice : choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「ジャワ」
```java title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
for (Choice choice : choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「C#」
```csharp title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
foreach (Choice choice in choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「行きます」
```go title=""
/* 回溯算法框架 */
func backtrack(state *State, choices []Choice, res *[]State) {
// 判断是否为解
if isSolution(state) {
// 记录解
recordSolution(state, res)
// 停止继续搜索
return
}
// 遍历所有选择
for _, choice := range choices {
// 剪枝:判断选择是否合法
if isValid(state, choice) {
// 尝试:做出选择,更新状态
makeChoice(state, choice)
backtrack(state, choices, res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice)
}
}
}
```
=== 「スウィフト」
```swift title=""
/* 回溯算法框架 */
func backtrack(state: inout State, choices: [Choice], res: inout [State]) {
// 判断是否为解
if isSolution(state: state) {
// 记录解
recordSolution(state: state, res: &res)
// 停止继续搜索
return
}
// 遍历所有选择
for choice in choices {
// 剪枝:判断选择是否合法
if isValid(state: state, choice: choice) {
// 尝试:做出选择,更新状态
makeChoice(state: &state, choice: choice)
backtrack(state: &state, choices: choices, res: &res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state: &state, choice: choice)
}
}
}
```
=== 「JS」
```javascript title=""
/* 回溯算法框架 */
function backtrack(state, choices, res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
for (let choice of choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「TS」
```typescript title=""
/* 回溯算法框架 */
function backtrack(state: State, choices: Choice[], res: State[]): void {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
for (let choice of choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「ダーツ」
```dart title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice>, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
// 停止继续搜索
return;
}
// 遍历所有选择
for (Choice choice in choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== 「錆」
```rust title=""
```
=== 「C」
```c title=""
/* 回溯算法框架 */
void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res, numRes);
// 停止继续搜索
return;
}
// 遍历所有选择
for (int i = 0; i < numChoices; i++) {
// 剪枝:判断选择是否合法
if (isValid(state, &choices[i])) {
// 尝试:做出选择,更新状态
makeChoice(state, &choices[i]);
backtrack(state, choices, numChoices, res, numRes);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, &choices[i]);
}
}
}
```
=== 「ジグ」
```zig title=""
```
次に、フレームワーク コードに基づいて問題例 3 を解きます。状態はstate
ノードのパスをトラバースし、choices
現在のノードの左の子ノードと右の子ノードを選択し、その結果がres
パス リストになります。
=== 「パイソン」
```python title="preorder_traversal_iii_template.py"
[class]{}-[func]{is_solution}
[class]{}-[func]{record_solution}
[class]{}-[func]{is_valid}
[class]{}-[func]{make_choice}
[class]{}-[func]{undo_choice}
[class]{}-[func]{backtrack}
```
=== 「C++」
```cpp title="preorder_traversal_iii_template.cpp"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「ジャワ」
```java title="preorder_traversal_iii_template.java"
[class]{preorder_traversal_iii_template}-[func]{isSolution}
[class]{preorder_traversal_iii_template}-[func]{recordSolution}
[class]{preorder_traversal_iii_template}-[func]{isValid}
[class]{preorder_traversal_iii_template}-[func]{makeChoice}
[class]{preorder_traversal_iii_template}-[func]{undoChoice}
[class]{preorder_traversal_iii_template}-[func]{backtrack}
```
=== 「C#」
```csharp title="preorder_traversal_iii_template.cs"
[class]{preorder_traversal_iii_template}-[func]{isSolution}
[class]{preorder_traversal_iii_template}-[func]{recordSolution}
[class]{preorder_traversal_iii_template}-[func]{isValid}
[class]{preorder_traversal_iii_template}-[func]{makeChoice}
[class]{preorder_traversal_iii_template}-[func]{undoChoice}
[class]{preorder_traversal_iii_template}-[func]{backtrack}
```
=== 「行きます」
```go title="preorder_traversal_iii_template.go"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrackIII}
```
=== 「スウィフト」
```swift title="preorder_traversal_iii_template.swift"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「JS」
```javascript title="preorder_traversal_iii_template.js"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「TS」
```typescript title="preorder_traversal_iii_template.ts"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「ダーツ」
```dart title="preorder_traversal_iii_template.dart"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「錆」
```rust title="preorder_traversal_iii_template.rs"
[class]{}-[func]{is_solution}
[class]{}-[func]{record_solution}
[class]{}-[func]{is_valid}
[class]{}-[func]{make_choice}
[class]{}-[func]{undo_choice}
[class]{}-[func]{backtrack}
```
=== 「C」
```c title="preorder_traversal_iii_template.c"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== 「ジグ」
```zig title="preorder_traversal_iii_template.zig"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
質問の意味によると、値7 7が見つかりました。検索はノード7以降も続行する必要があるため、レコード ソリューションの後のステートメントをreturn
削除する。次の図は、return
ステートメントを保持または削除する場合の検索プロセスを比較しています。
プリオーダートラバーサルに基づくコード実装と比較すると、バックトラッキングアルゴリズムフレームワークに基づくコード実装は冗長に見えますが、より多用途です。実際、多くのバックトラッキング問題は、このフレームワークの下で解決できます。特定の問題に従ってstate
と を定義しchoices
、フレームワークに各メソッドを実装するだけです。
共通用語
アルゴリズムの問題をより明確に分析するために、バックトラッキング アルゴリズムで一般的に使用される用語の意味を要約し、例 3 と比較した対応する例を示します。
表 バックトラッキング アルゴリズムの一般的な用語
名詞 | 意味 | 例 3 |
---|---|---|
解決 | 解決策とは、問題の特定の条件を満たす回答です。これは 1 つ以上の場合があります。 | ルートノードからノード7 77.制約を満たすすべてのパス |
制約 | 制約は、解決策の実現可能性を制限する問題内の条件であり、通常は枝刈りに使用されます。 | ノード3 3はパスに含まれていません3 |
州州 | ステータスは、行われた選択を含む、特定の時点での問題の状況を表します。 | 現在訪問しているノードのパス、つまりpath ノードのリスト |
試してみる | 試行とは、選択を行う、状態を更新する、それが解決策であるかどうかを確認するなど、利用可能な選択肢に基づいて解決策空間を探索するプロセスです。 | 左(右)の子ノードに再帰的にアクセスし、ノードを追加しpath 、ノードの値が7 7であるかどうかを判断します。7 |
後戻り | ロールバックとは、制約を満たさない状態に遭遇したときに、前の選択をキャンセルして前の状態に戻ることを意味します。 | リーフ ノードを通過してノード アクセスを終了すると、値は3 3になります。ノード3に到達すると検索は終了し、関数は戻ります。 |
剪定 | 枝刈りは、問題の特性と制約に基づいて無意味な検索パスを回避する方法であり、検索効率を向上させることができます。 | 値3 3に遭遇した場合ノードが3に達すると、検索は終了します。 |
!!! ヒント
问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。
利点と制限
バックトラッキング アルゴリズムは基本的に、条件を満たす解決策が見つかるまですべての可能な解決策を試行する深さ優先検索アルゴリズムです。この方法の利点は、考えられるすべての解決策を見つけることができ、合理的な枝刈り操作の下では非常に効率的であることです。
ただし、大規模な問題や複雑な問題を扱う場合、バックトラッキング アルゴリズムの効率が許容できない場合があります。
- 時間: バックトラッキング アルゴリズムは通常、状態空間のすべての可能性を横断する必要があり、時間計算量は指数関数または階乗オーダーに達する可能性があります。
- スペース: 現在の状態 (パス、プルーニング用の補助変数など) を再帰呼び出しで保存する必要があります。深さが大きい場合、必要なスペースが非常に大きくなる可能性があります。
それでも、バックトラッキング アルゴリズムは、依然として一部の検索問題や制約充足問題に対する最良の解決策です。これらの問題では、どの選択肢が有効な解決策を生み出すかを予測することが不可能であるため、考えられるすべての選択肢を反復する必要があります。この場合、いかに効率を最適化するかが鍵となり、一般的な効率最適化手法は 2 つあります。
- 枝刈り: 確実に解決策が得られないパスの検索を回避することで、時間とスペースを節約します。
- ヒューリスティック検索: 検索プロセス中にいくつかの戦略または推定を導入して、効果的なソリューションを生み出す可能性が最も高いパスの検索に優先順位を付けます。
典型的な例を確認する
バックトラッキング アルゴリズムを使用すると、多くの検索問題、制約充足問題、および組み合わせ最適化問題を解決できます。
問題の検索: このタイプの問題の目的は、特定の条件を満たす解決策を見つけることです。
- 全順列問題: 与えられたセットで、考えられるすべての順列と組み合わせを見つけます。
- サブセット合計の問題: セットとターゲット合計が与えられた場合、セット内の合計がターゲット合計となるサブセットを見つけます。
- ハノイの塔の問題: 3 本の柱とさまざまなサイズの一連のディスクがあるとすると、すべてのディスクを 1 つの柱から別の柱に移動する必要があります。一度に移動できるディスクは 1 つだけであり、大きなディスクは上に置くことができません。小さなディスク。
制約満足問題: このタイプの問題の目標は、すべての制約を満たす解決策を見つけることです。
- んnクイーン:n × nn \times nn×nのチェス盤にnnを置きますn女王は互いに攻撃しないようにします。
- 数独: 9 × 9 9 \times 99×9のマス目を1 1 の1~9 99、つまり、各行、各列、および各3 × 3 3 \times 33×3 つのサブグリッドの数字は
- グラフの色付けの問題: 無向グラフが与えられた場合、隣接する頂点が異なる色になるように、グラフの各頂点を最も少ない色で色付けします。
組み合わせ最適化問題: このタイプの問題の目的は、組み合わせ空間で特定の条件を満たす最適な解を見つけることです。
- 0-1 ナップザック問題: アイテムとバックパックのセットが与えられ、各アイテムには一定の価値と重量があり、バックパックの容量制限内で合計値が最大になるようにアイテムを選択する必要があります。
- 巡回セールスマンの問題: グラフ内で、ある点から開始し、他のすべての点を 1 回だけ訪問し、開始点に戻って最短経路を見つけます。
- 最大クリーク問題: 無向グラフが与えられた場合、最大の完全な部分グラフ、つまり部分グラフ内の任意の 2 つの頂点がエッジで接続されている部分を見つけます。
多くの組み合わせ最適化問題では、バックトラッキングは最適な解決策ではないことに注意してください。
- 0-1 ナップザック問題は通常、より高い時間効率を達成するために動的計画法を使用して解決されます。
- 巡回セールスマンはよく知られた NP ハード問題であり、一般的に使用される解決策には、遺伝的アルゴリズムやアリのコロニー アルゴリズムが含まれます。
- 最大クリーク問題はグラフ理論の古典的な問題であり、グリーディなどのヒューリスティック アルゴリズムによって解決できます。