This commit is contained in:
krahets
2023-07-16 04:18:52 +08:00
parent c342ba3ced
commit edcd1e5c10
21 changed files with 475 additions and 67 deletions
@@ -2,7 +2,7 @@
comments: true
---
# 12.1.   回溯算法
# 13.1.   回溯算法
「回溯算法 Backtracking Algorithm」是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。
@@ -169,7 +169,7 @@ comments: true
<p align="center"> Fig. 在前序遍历中搜索节点 </p>
## 12.1.1. &nbsp; 尝试与回退
## 13.1.1. &nbsp; 尝试与回退
**之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。
@@ -405,7 +405,7 @@ comments: true
=== "<11>"
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png)
## 12.1.2. &nbsp; 剪枝
## 13.1.2. &nbsp; 剪枝
复杂的回溯问题通常包含一个或多个约束条件,**约束条件通常可用于“剪枝”**。
@@ -616,7 +616,7 @@ comments: true
<p align="center"> Fig. 根据约束条件剪枝 </p>
## 12.1.3. &nbsp; 常用术语
## 13.1.3. &nbsp; 常用术语
为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。
@@ -633,7 +633,7 @@ comments: true
解、状态、约束条件等术语是通用的,适用于回溯算法、动态规划、贪心算法等。
## 12.1.4. &nbsp; 框架代码
## 13.1.4. &nbsp; 框架代码
回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。为提升代码通用性,我们希望将回溯算法的“尝试、回退、剪枝”的主体框架提炼出来。
@@ -1350,7 +1350,7 @@ comments: true
相较于基于前序遍历的实现代码,基于回溯算法框架的实现代码虽然显得啰嗦,但通用性更好。实际上,**所有回溯问题都可以在该框架下解决**。我们需要根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法。
## 12.1.5. &nbsp; 优势与局限性
## 13.1.5. &nbsp; 优势与局限性
回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优势在于它能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。
@@ -1364,7 +1364,7 @@ comments: true
- 上文介绍过的剪枝是一种常用的优化方法。它可以避免搜索那些肯定不会产生有效解的路径,从而节省时间和空间。
- 另一个常用的优化方法是加入「启发式搜索 Heuristic Search」策略,它在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。
## 12.1.6. &nbsp; 典型例题
## 13.1.6. &nbsp; 典型例题
**搜索问题**:这类问题的目标是找到满足特定条件的解决方案。
+1 -1
View File
@@ -2,7 +2,7 @@
comments: true
---
# 12. &nbsp; 回溯
# 13. &nbsp; 回溯
<div class="center-table" markdown>
+2 -2
View File
@@ -2,7 +2,7 @@
comments: true
---
# 12.4. &nbsp; N 皇后问题
# 13.4. &nbsp; N 皇后问题
!!! question
@@ -496,7 +496,7 @@ comments: true
[class]{}-[func]{nQueens}
```
## 12.4.1. &nbsp; 复杂度分析
## 13.4.1. &nbsp; 复杂度分析
逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n, n-1, \cdots, 2, 1$ 个选择,**因此时间复杂度为 $O(n!)$** 。实际上,根据对角线约束的剪枝也能够大幅地缩小搜索空间,因而搜索效率往往优于以上时间复杂度。
+4 -4
View File
@@ -2,7 +2,7 @@
comments: true
---
# 12.2. &nbsp; 全排列问题
# 13.2. &nbsp; 全排列问题
全排列问题是回溯算法的一个典型应用。它的定义是在给定一个集合(如一个数组或字符串)的情况下,找出这个集合中元素的所有可能的排列。
@@ -18,7 +18,7 @@ comments: true
</div>
## 12.2.1. &nbsp; 无重复的情况
## 13.2.1. &nbsp; 无重复的情况
!!! question
@@ -352,7 +352,7 @@ comments: true
<p align="center"> Fig. 全排列剪枝示例 </p>
## 12.2.2. &nbsp; 考虑重复的情况
## 13.2.2. &nbsp; 考虑重复的情况
!!! question
@@ -710,7 +710,7 @@ comments: true
<p align="center"> Fig. 两种剪枝条件的作用范围 </p>
## 12.2.3. &nbsp; 复杂度分析
## 13.2.3. &nbsp; 复杂度分析
假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。因此,**时间复杂度为 $O(n!n)$** 。
+4 -4
View File
@@ -2,7 +2,7 @@
comments: true
---
# 12.3. &nbsp; 子集和问题
# 13.3. &nbsp; 子集和问题
!!! question
@@ -10,7 +10,7 @@ comments: true
例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,由于集合中的数字可以被重复选取,因此解为 $\{3, 3, 3\}, \{4, 5\}$ 。请注意,子集是不区分元素顺序的,例如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一个子集。
## 12.3.1. &nbsp; 从全排列引出解法
## 13.3.1. &nbsp; 从全排列引出解法
类似于上节全排列问题的解法,我们可以把子集的生成过程想象成一系列选择的结果,并在选择过程中实时更新“元素和”,当元素和等于 `target` 时,就将子集记录至结果列表。
@@ -274,7 +274,7 @@ comments: true
<p align="center"> Fig. 子集搜索与越界剪枝 </p>
## 12.3.2. &nbsp; 重复子集剪枝
## 13.3.2. &nbsp; 重复子集剪枝
为了去除重复子集,**一种直接的思路是对结果列表进行去重**。但这个方法效率很低,因为:
@@ -569,7 +569,7 @@ comments: true
<p align="center"> Fig. 子集和 I 回溯过程 </p>
## 12.3.3. &nbsp; 相等元素剪枝
## 13.3.3. &nbsp; 相等元素剪枝
!!! question
+1 -1
View File
@@ -2,7 +2,7 @@
comments: true
---
# 12.5. &nbsp; 小结
# 13.5. &nbsp; 小结
- 回溯算法本质是穷举法,通过对解空间进行深度优先遍历来寻找符合条件的解。在搜索过程中,遇到满足条件的解则记录,直至找到所有解或遍历完成后结束。
- 回溯算法的搜索过程包括尝试与回退两个部分。它通过深度优先搜索来尝试各种选择,当遇到不满足约束条件的情况时,则撤销上一步的选择,退回到之前的状态,并继续尝试其他选择。尝试与回退是两个方向相反的操作。