Re-translate the Japanese version (#1871)

* Retranslate Japanese docs with GPT-5.4

* Retranslate Japanese code with GPT-5.4
This commit is contained in:
Yudong Jin
2026-03-30 07:30:15 +08:00
committed by GitHub
parent fe6443235b
commit d7b2277d2b
1444 changed files with 83312 additions and 8363 deletions

View File

@@ -1,45 +1,45 @@
# バックトラッキングアルゴリズム
<u>バックトラッキングアルゴリズム</u>は全数探索によって問題を解決する方法です。その核心概念は、初期状態から開始してすべての可能な解を総当たりで探索することです。アルゴリズムは正しいものを記録し、解見つるか、すべての可能な解が試されたが解が見つからなまで続けす。
<u>バックトラッキングアルゴリズムbacktracking algorithm</u>は、総当たりによって問題を解く手法です。その中核となる考え方は、初期状態から出発し、あり得るすべての解を力任せに探索し、正しい解に到達したらそれを記録し、解見つるか、考えられるすべての選択を試しても解が見つからなくなるまで続ける、というものです。
バックトラッキング通常「深さ優先探索」を使用して解空間を走査します。「二分木」の章で、前順中順後順走査はすべて深さ優先探索であることを述べました。次に、前順走査を使用してバックトラッキング問題を解決し、アルゴリズムの動作を段階的に理解していきます。
バックトラッキングアルゴリズムでは、通常「深さ優先探索」を用いて解空間をたどります。「二分木」の章で述べたように、前順中順後順走査はいずれも深さ優先探索に属します。ここでは前順走査を使てバックトラッキング問題を構成し、その仕組みを段階的に理解していきます。
!!! question "例1"
!!! question "例1"
二分木が与えられた場合、値が $7$ のすべてのノードを検索して記録し、リスト返してください。
1 本の二分木が与えられたとき、値が $7$ のノードをすべて探索して記録し、そのノードのリスト返してください。
この問題を解決するために、この木を前順走査し、現在のノードの値が $7$ かどうかを確認します。そうであれば、ノードの値を結果リスト `res` に追加します。プロセスは以下の図に示されています:
この問題では、この木を前順走査し、現在のノードの値が $7$ かどうかを判定します。該当する場合は、そのノードの値を結果リスト `res` に追加します。関連する処理は下図と次のコードのとおりです。
```src
[file]{preorder_traversal_i_compact}-[class]{}-[func]{pre_order}
```
![前順走査でノード検索](backtracking_algorithm.assets/preorder_find_nodes.png)
![前順走査でノードを探索する](backtracking_algorithm.assets/preorder_find_nodes.png)
## 試行と後退
## 試行と戻る
**解空間を探索する際に「試行」と「後退」戦略を使用するため、バックトラッキングアルゴリズムと呼ばれます**。探索中、満足のいく解を得るためにもはや進めない状態に遭遇するたびに、前の選択を取り消して前の状態戻り、次の試行のために他の可能な選択を選択できるようにします。
**バックトラッキングアルゴリズムと呼ばれるのは、解空間を探索する際に「試行」と「戻る」という戦略を取るためです**。探索中に、ある状態から先へ進めない、または条件を満たす解を得られないと分かった場合、アルゴリズムは直前の選択を取り消して前の状態戻り、別の選択肢を試します。
例1では、各ードの訪問が「試行」を開始します。そして葉ノードを通過するか、`return` 文で親ノードに戻ることが「後退」を示唆します。
1では、各ードの訪問が 1 回の「試行」に対応し、葉ノードを越えるか親ノードへ戻る `return` は「戻る」を表します。
**後退は単に関数の戻り値ではないことに注意してください**。例1の問題を少し拡張して、それが何を意味するかを説明します。
ここで強調しておきたいのは、**戻るとは関数の return だけを指すわけではない**という点です。これを説明するために、例題1を少し拡張します。
!!! question "例2"
!!! question "例2"
二分木で値が $7$ のすべてのノードを検索し、すべてのマッチングノードについて、**ルートノードからそのノードまでのパスを返してください**。
二分木の中で値が $7$ のノードをすべて探索し、**根ノードからそれらのノードまでの経路を返してください**。
例1のコードに基づいて、訪問したノードパスを記録するため `path` というリストを使用する必要があります。値が $7$ のノードに到達すると`path` をコピーして結果リスト `res` に追加します。走査`res` にはすべての解が保されます。コードは以下の通りです
1のコードを土台に、訪問済みノードの経路を記録するためのリスト `path` を導入します。値が $7$ のノードに到達したら`path` をコピーして結果リスト `res` に追加します。走査が完了すると`res` にはすべての解が保されています。コードは次のとおりです
```src
[file]{preorder_traversal_ii_compact}-[class]{}-[func]{pre_order}
```
各「試行」で現在のノードを `path` に追加することでパスを記録します。「後退」が必要なときはいつでも、`path` からノードをポップして**この失敗した試行前の状態を復元します**
各「試行」で現在のノードを `path` に追加して経路を記録し、「戻る」前にはそのノードを `path` から取り除き、**今回の試行前の状態を復元する**必要があります
以下の図に示すプロセスを観察することで、**試行は「前進」のようで、後退は「元に戻す」のようです**。後者のペアは、対応するものに対する逆操作と見なすことができます。
の図に示す過程を見ると、**試行と戻るは「前進」と「取り消し」として理解できます**。この 2 つの操作は互いに逆向きです。
=== "<1>"
![試行と後退](backtracking_algorithm.assets/preorder_find_paths_step1.png)
![試行と戻る](backtracking_algorithm.assets/preorder_find_paths_step1.png)
=== "<2>"
![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png)
@@ -71,72 +71,72 @@
=== "<11>"
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png)
## 剪定
## 枝刈り
複雑なバックトラッキング問題は通常1つ以上の制約を含み、**これらは「剪定」によく使用されます**。
複雑なバックトラッキング問題には、通常 1 つ以上の制約条件が含まれます。**制約条件は多くの場合「枝刈り」に利用できます**。
!!! question "例3"
!!! question "例3"
二分木で値が $7$ のすべてのノードを検索し、ルートかられらのノードまでのパスを返してください。**ただし、パスには値が $3$ のノードを含まないという制限があります**。
二分木の中で値が $7$ のノードをすべて探索し、根ノードかられらのノードまでの経路を返してください。**ただし、経路には値が $3$ のノードを含めてはいけません**。
の制約を満たすために、**剪定操作を追加する必要があります**:検索プロセス中に値が $3$ のノードに遭遇した場合、そのパスを通じてさらに検索することを即座に中止します。コードは以下の通りです
上の制約条件を満たすために、**枝刈り操作を追加する必要があります**。探索中に値が $3$ のノードに出会った場合、そこで早めに return し、それ以上探索を続けません。コードは次のとおりです
```src
[file]{preorder_traversal_iii_compact}-[class]{}-[func]{pre_order}
```
剪定」は非常に生き生きとした名詞です。以下の図に示すように、検索プロセスで、**制約を満たさない索分岐を切り取り」ます**。さらなる不要な試行を避け、索効率を向上させます。
枝刈り」は非常にイメージしやすい名称です。次の図のように、探索中に**制約条件を満たさない索分岐を切り落とす**ことで、多くの無意味な試行を避け、索効率を高められます。
![制約に基づく剪定](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
![制約条件にもとづく枝刈り](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
## フレームワークコード
今度は、バックトラッキングから「試行、後退、剪定」の主要なフレームワークを抽出して、コードの汎用性を向上させてみましょう
次に、バックトラッキングにおける「試行・戻る・枝刈り」の本体部分を抽出し、汎用性の高いコードフレームワークへまとめてみます
以下のフレームワークコードでは、`state` は問題の現在状態を表し`choices`現在の状態で利用可能な選択肢を表します
以下のフレームワークコードでは、`state` は問題の現在状態、`choices`の状態で取り得る選択肢を表します
=== "Python"
```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);
}
}
@@ -146,23 +146,23 @@
=== "Java"
```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);
}
}
@@ -172,23 +172,23 @@
=== "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);
}
}
@@ -198,23 +198,23 @@
=== "Go"
```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)
}
}
@@ -224,23 +224,23 @@
=== "Swift"
```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)
}
}
@@ -250,23 +250,23 @@
=== "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);
}
}
@@ -276,23 +276,23 @@
=== "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);
}
}
@@ -302,23 +302,23 @@
=== "Dart"
```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);
}
}
@@ -328,23 +328,23 @@
=== "Rust"
```rust title=""
/* バックトラッキングアルゴリズムフレームワーク */
/* バックトラッキングアルゴリズムフレームワーク */
fn backtrack(state: &mut State, choices: &Vec<Choice>, res: &mut Vec<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);
}
}
@@ -354,23 +354,23 @@
=== "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]);
}
}
@@ -380,23 +380,23 @@
=== "Kotlin"
```kotlin title=""
/* バックトラッキングアルゴリズムフレームワーク */
/* バックトラッキングアルゴリズムフレームワーク */
fun backtrack(state: State?, choices: List<Choice?>, res: List<State?>?) {
// 解かどうかを確認
// 解かどうかを判定
if (isSolution(state)) {
// 解を記録
recordSolution(state, res)
// 検索を停止
// これ以上探索しない
return
}
// すべての選択肢を反復
// すべての選択肢を走査
for (choice in choices) {
// 剪定:選択肢が有効かどうかを確認
// 枝刈り: 選択が妥当かを判定
if (isValid(state, choice)) {
// 試行選択を行い、状態を更新
// 試行: 選択を行い、状態を更新
makeChoice(state, choice)
backtrack(state, choices, res)
// 後退:選択を取り消し、前の状態に戻す
// 戻る: 選択を取り消し、前の状態に戻す
undoChoice(state, choice)
}
}
@@ -406,98 +406,98 @@
=== "Ruby"
```ruby title=""
### バックトラッキングアルゴリズムフレームワーク ###
### バックトラッキングアルゴリズムフレームワーク ###
def backtrack(state, choices, res)
# 解かどうかを確認
# 解かどうかを判定
if is_solution?(state)
# 解を記録
record_solution(state, res)
return
end
# すべての選択肢を反復
# すべての選択肢を走査
for choice in choices
# 剪定:選択肢が有効かどうかを確認
# 枝刈り: 選択が妥当かを判定
if is_valid?(state, choice)
# 試行選択を行い、状態を更新
# 試行: 選択を行い、状態を更新
make_choice(state, choice)
backtrack(state, choices, res)
# 後退:選択を取り消し、前の状態に戻す
# 戻る: 選択を取り消し、前の状態に戻す
undo_choice(state, choice)
end
end
end
```
次に、フレームワークコードに基づいて例題 3 を解きます。状態 `state` はノードの走査経路を表し、選択肢 `choices` は現在ノードの左子ノードと右子ノード、結果 `res` は経路リストです
次に、このフレームワークコードを用いて例題3を解きます。状態 `state` はノードの走査経路、選択肢 `choices` は現在ノードの左子ノードと右子ノード、結果 `res` は経路リストです
```src
[file]{preorder_traversal_iii_template}-[class]{}-[func]{backtrack}
```
問題文の意味に従い、値が $7$ のノードを見つけた後も探索を続ける必要があります。**したがって、解を記録した後の `return` 文削除する必要があります**。次の図は、`return` 文を保持する場合と削除する場合の探索過程比較です。
問題の条件より、値が $7$ のノードを見つけた後も探索を続ける必要があります。**そのため、解を記録した後の `return` 文削除しなければなりません**。次の図は、`return` 文を残す場合と削除する場合の探索過程比較したものです。
![returnを保持する場合と削除する場合の探索過程の比較](backtracking_algorithm.assets/backtrack_remove_return_or_not.png)
![return を残す場合と削除する場合の探索過程の比較](backtracking_algorithm.assets/backtrack_remove_return_or_not.png)
前順走査に基づくコード実装と比べると、バックトラッキングアルゴリズムのフレームワークにづく実装はやや冗長に見えますが、汎用性はより高いです。実際、**多くのバックトラッキング問題はこのフレームワークの下で解くことができます**。具体的な問題に応じて `state` と `choices` を定義し、フレームワーク内の各メソッドを実装すればよいのです。
前順走査にもとづく実装と比べると、バックトラッキングアルゴリズムのフレームワークにもとづく実装はやや冗長に見えますが、汎用性に優れています。実際、**多くのバックトラッキング問題はこのフレームワークで解けます**。具体的な問題に応じて `state` と `choices` を定義し、各メソッドを実装すれば十分です。
## よく使われる用語
アルゴリズム問題をより明確に分析するために、バックトラッキングアルゴリズムでよく使われる用語の意味をまとめ、例題 3 の対応例を以下の表に示します。
アルゴリズム問題をより明確に分析するために、バックトラッキングでよく使われる用語の意味を整理し、例題3に対応する例を次の表にまとめます。
<p align="center"> 表 <id> &nbsp; バックトラッキングアルゴリズムでよく使われる用語 </p>
<p align="center"> 表 <id> &nbsp; よく使われるバックトラッキング用語 </p>
| 名称 | 定義 | 例題 3 |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| 解solution | 解は問題の特定条件を満たす答えであり、1 つまたは複数存在する可能性がある | 根ノードからノード $7$ までの制約条件を満たすすべての経路 |
| 制約条件constraint | 制約条件は、解の実現可能性を制限する条件であり、通常は枝刈りに使用される | 経路にノード $3$ を含まない |
| 状態state | 状態は、ある時点での問題の状況を表し、これまでに行った選択を含む | 現在訪問したノード経路、すなわち `path` ノードリスト |
| 試行attempt | 試行は、利用可能な選択肢にづいて解空間を探索する過程であり、選択を行い、状態更新、解かどうかを確認する | 左(右)子ノードを再帰的に訪問し、ノードを `path` に追加し、ノードの値が $7$ かを確認する |
| バックトラックbacktracking | 制約条件を満たさない状態に遭遇した場合、以前の選択を取り消して前の状態戻ること | 葉ノードを越えたとき、探索終了、値が $3$ のノードに遭遇したとき探索を終了し、関数が戻る |
| 枝刈りpruning | 問題の性や制約条件にづき、無意味な探索経路を避ける方法であり、探索効率を向上させる | 値が $3$ のノードに遭遇した場合、それ以上探索しない |
| 用語 | 定義 | 例題3 |
| ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 解solution | 問題の特定条件を満たす答えであり、1 つまたは複数存在し得る | 根ノードからノード $7$ までの制約条件を満たすすべての経路 |
| 制約条件constraint | 解の実現可能性を制限する条件であり、通常は枝刈りに用いられる | 経路にノード $3$ を含まないこと |
| 状態state | ある時点における問題の状況を表し、でに行った選択を含む | 現在までに訪問したノード経路、すなわち `path` ノードリスト |
| 試行attempt | 利用可能な選択肢にもとづいて解空間を探索する過程であり、選択、状態更新、解判定を含む | 左右の子ノードを再帰的に訪問し、ノードを `path` に追加し、値が $7$ か判定する |
| 戻るbacktracking | 制約条件を満たさない状態に出会ったとき、それまでの選択を取り消して前の状態戻ること | 葉ノードを越えたとき、ノード訪問を終えたとき、値が $3$ のノードに出会ったとき探索を終了し、関数から戻る |
| 枝刈りpruning | 問題の性や制約条件にもとづき、無意味な探索経路を避ける方法であり、探索効率を高める | 値が $3$ のノードに出会ったら、それ以上探索しない |
!!! tip
問題、解、状態などの概念は一般的なものであり、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムにも関係します。
問題、解、状態などの概念は汎用的であり、分割統治、バックトラッキング、動的計画法、貪欲法などのアルゴリズムにも共通して現れます。
## 長所と限界
## 利点と限界
バックトラッキングアルゴリズム本質的に深さ優先探索DFSアルゴリズムの一種であり、条件を満たす解を見つけるまであらゆる可能な解を試ます。この方法の利点は、すべての可能な解を見つけられるであり、適切な枝刈りを行えば効率が高いことです。
バックトラッキングアルゴリズム本質深さ優先探索です。条件を満たす解を見つけるまで、あり得るすべての解を試ます。この方法の利点は、考えられるすべての解を見つけられることであり、適切な枝刈りを行えば高い効率を発揮します。
しかし、大規模または複雑な問題を扱う場合、**バックトラッキングアルゴリズムの実行効率は許容できないほど低下する可能性があります**。
しかし、大規模または複雑な問題を扱う場合、**バックトラッキングアルゴリズムの実行効率は受け入れがたいことがあります**。
- **時間**:バックトラッキングアルゴリズムは通常、状態空間のすべての可能性を探索する必要があり、時間計算量は指数オーダーまたは階乗オーダーに達する可能性があります。
- **空間**:再帰呼び出し中に現在の状態(例:経路枝刈り用の補助変数など)を保する必要があり、深さが大きい場合、空間使用量が増加します。
- **時間**:バックトラッキングアルゴリズムは通常、状態空間のすべての可能性をたどる必要があり、時間計算量は指数時間や階乗時間に達することがあります。
- **空間**:再帰呼び出しの過程では現在の状態(たとえば経路枝刈り用の補助変数など)を保する必要があり、深さが大きい空間使用量も大きくなります。
それでもなお、**バックトラッキングアルゴリズムは特定の探索問題や制約足問題最良の解法であることが多いです**。これらの問題では、どの選択が有効な解を生成するかを予測できないため、すべての可能な選択を試す必要があります。このような場合、**効率最適化が鍵**となります。一般的な最適化手法は次の 2 つす。
それでもなお、**バックトラッキングアルゴリズムは一部の探索問題や制約足問題に対する最良の解法です**。この種の問題では、どの選択が有効な解を生むかを事前に予測できないため、可能な選択肢をすべてたどる必要があります。このときの鍵は**いかに効率最適化するか**であり、代表的な方法は 2 つあります。
- **枝刈り**:解を生成しないことが確実な経路を避けることで、時間と空間を節約します。
- **ヒューリスティック探索**:探索中に戦略や評価値を導入し、有効な解を生成する可能性が高い経路を優先的に探索します。
- **枝刈り**:解が生じないことが確実な経路を探索しないことで、時間と空間を節約す
- **ヒューリスティック探索**:探索中に何らかの戦略や推定値を導入し、有効な解を生みやすい経路を優先的に探索す
## バックトラッキングの典型的な例題
## バックトラッキングの典型例題
バックトラッキングアルゴリズムは、多くの探索問題、制約足問題、組合せ最適化問題を解くのに使用できます。
バックトラッキングアルゴリズムは、多くの探索問題、制約足問題、組合せ最適化問題の解決に利用できます。
**探索問題**:この種の問題の目標は、特定の条件を満たす解を見つけることです。
- 全順列問題:与えられた集合のすべての可能な順列を求める。
- 部分和問題:与えられた集合と目標和に対して、和が目標値なるすべての部分集合を求める。
-イの塔3 本の柱と異なるサイズの円盤があり、すべての円盤を 1 本の柱から別の柱に移す。1 回に 1 枚しか動かせず、大き円盤を小さい円盤の上に置くことはできない。
- 全順列問題:ある集合が与えられたとき、考えられるすべての順列を求める。
- 部分和問題:ある集合と目標和が与えられたとき、和が目標値なるすべての部分集合を見つける。
- ハノイの塔問題3 本の柱と大きさの異なる複数の円盤が与えられたとき、すべての円盤を 1 本の柱から別の柱へ移動する。ただし 1 回に 1 枚しか動かせず、大き円盤を小さい円盤の上に置いてはならない。
**制約足問題**:この種の問題の目標は、すべての制約条件を満たす解を見つけることです。
**制約足問題**:この種の問題の目標は、すべての制約条件を満たす解を見つけることです。
- $n$ クイーン問題:$n imes n$ のチェス盤に $n$ 個のクイーンを配置し、互いに攻撃しないようにする。
- 数独:$9 imes 9$ のグリッドに数字 $1$ \~ $9$ を入力し、各行、列、$3 imes 3$ のサブグリッドに重複ないようにする。
- グラフ彩色問題:与えられた無向グラフに対し、隣接頂点が異なる色になるように最小限の色で彩色する。
- $n$ クイーン問題:$n \times n$ の盤に $n$ 個のクイーンを配置し、互いに攻撃し合わないようにする。
- 数独:$9 \times 9$ のグリッドに数字 $1$ ~ $9$ を入、各行・各列・各 $3 \times 3$ の小区画で数字が重複ないようにする。
- グラフ彩色問題:無向グラフが与えられたとき、隣接する頂点が同じ色にならないように、できるだけ少ない色で各頂点を彩色する。
**組合せ最適化問題**:この種の問題の目標は、組合せ空間内で特定の条件を満たす最適解を見つけることです。
**組合せ最適化問題**:この種の問題の目標は、組合せ空間の中で条件を満たす最適解を見つけることです。
- 0-1 ナップサック問題:与えられた物品群とバックパックがあり、各物品には価値と重さが設定されている。バックパック容量制限内で総価値最大化する物品の選択を求める
- 旅行セールスマン問題:グラフ上で、1 つの点から出発し、すべての他の点を 1 回ずつ訪問して出発点戻る最短経路を求める。
- 最大クリーク問題:与えられた無向グラフの中で、任意の 2 頂点間に辺が存在する最大の完全部分グラフを見つける。
- 0-1 ナップサック問題:複数の品物とナップサックが与えられ、各品物には価値と重さがある。ナップサック容量の範囲内で総価値最大になるように品物を選ぶ
- 巡回セールスマン問題:グラフ内のある頂点から出発し、他のすべての頂点をちょうど 1 回ずつ訪て出発点戻るときの最短経路を求める。
- 最大クリーク問題:無向グラフが与えられたとき、任意の 2 頂点間に辺が存在する最大の完全部分グラフを見つける。
注意すべきは、多くの組合せ最適化問題に対して、バックトラッキング最適解法ではないということです
多くの組合せ最適化問題では、バックトラッキング最適解法ではない点に注意してください
- 0-1 ナップサック問題は時間効率を高めるために動的計画法がよく使用されます
- 旅行セールスマン問題は名な NP-Hard 問題であり、遺伝的アルゴリズムやアントコロニーアルゴリズムなどの手法がよく使われます
- 最大クリーク問題はグラフ理論古典的問題であり、貪欲法などのヒューリスティックアルゴリズムで解くことができます
- 0-1 ナップサック問題は通常、より高い時間効率をるために動的計画法で解く
- 巡回セールスマン問題は名な NP-Hard 問題であり、よく用いられる解法には遺伝的アルゴリズムやコロニー最適化などがある
- 最大クリーク問題はグラフ理論における古典的問題であり、貪欲法などのヒューリスティックで解ける