This commit is contained in:
krahets
2023-06-02 02:38:24 +08:00
parent 874e75d92d
commit 2a85d796e6
35 changed files with 2354 additions and 0 deletions
@@ -159,6 +159,12 @@ comments: true
[class]{}-[func]{preOrder}
```
=== "Dart"
```dart title="preorder_traversal_i_compact.dart"
[class]{}-[func]{preOrder}
```
![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png)
<p align="center"> Fig. 在前序遍历中搜索节点 </p>
@@ -358,6 +364,12 @@ comments: true
[class]{}-[func]{preOrder}
```
=== "Dart"
```dart title="preorder_traversal_ii_compact.dart"
[class]{}-[func]{preOrder}
```
在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。换句话说,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为相反的。
=== "<1>"
@@ -592,6 +604,12 @@ comments: true
[class]{}-[func]{preOrder}
```
=== "Dart"
```dart title="preorder_traversal_iii_compact.dart"
[class]{}-[func]{preOrder}
```
剪枝是一个非常形象的名词。在搜索过程中,**我们利用约束条件“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提升搜索效率。
![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
@@ -848,6 +866,12 @@ comments: true
```
=== "Dart"
```dart title=""
```
下面,我们尝试基于此框架来解决例题三。在例题三中,状态 `state` 是节点遍历路径,选择 `choices` 是当前节点的左子节点和右子节点,结果 `res` 是路径列表,实现代码如下所示。
=== "Java"
@@ -1289,6 +1313,22 @@ comments: true
[class]{}-[func]{backtrack}
```
=== "Dart"
```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}
```
相较于基于前序遍历的实现代码,基于回溯算法框架的实现代码虽然显得啰嗦,但通用性更好。实际上,**所有回溯问题都可以在该框架下解决**。我们需要根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法。
## 12.1.5. &nbsp; 典型例题
+8
View File
@@ -488,6 +488,14 @@ comments: true
[class]{}-[func]{nQueens}
```
=== "Dart"
```dart title="n_queens.dart"
[class]{}-[func]{backtrack}
[class]{}-[func]{nQueens}
```
## 12.3.1. &nbsp; 复杂度分析
逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n, n-1, \cdots, 2, 1$ 个选择,**因此时间复杂度为 $O(n!)$** 。实际上,根据对角线约束的剪枝也能够大幅地缩小搜索空间,因而搜索效率往往优于以上时间复杂度。
@@ -336,6 +336,14 @@ comments: true
[class]{}-[func]{permutationsI}
```
=== "Dart"
```dart title="permutations_i.dart"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutationsI}
```
需要重点关注的是,我们引入了一个布尔型数组 `selected` ,它的长度与输入数组长度相等,其中 `selected[i]` 表示 `choices[i]` 是否已被选择。我们利用 `selected` 避免某个元素被重复选择,从而实现剪枝。
如下图所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1, 3 的分支。**从本质上理解,此剪枝操作可将搜索空间大小从 $O(n^n)$ 降低至 $O(n!)$** 。
@@ -683,6 +691,14 @@ comments: true
[class]{}-[func]{permutationsII}
```
=== "Dart"
```dart title="permutations_ii.dart"
[class]{}-[func]{backtrack}
[class]{}-[func]{permutationsII}
```
注意,虽然 `selected` 和 `duplicated` 都起到剪枝的作用,但他们剪掉的是不同的分支:
- **剪枝条件一**:整个搜索过程中只有一个 `selected` 。它记录的是当前状态中包含哪些元素,作用是避免某个元素在 `state` 中重复出现。