mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-26 11:16:28 +08:00
build
This commit is contained in:
parent
17e19ea4fe
commit
5bc8df6d5d
4 changed files with 33 additions and 8 deletions
|
@ -6,7 +6,7 @@ comments: true
|
|||
|
||||
「回溯算法 Backtracking Algorithm」是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。
|
||||
|
||||
回溯算法通常采用「深度优先搜索」来遍历解空间。在二叉树章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。下面,我们从二叉树的前序遍历入手,逐步了解回溯算法的工作原理。
|
||||
回溯算法通常采用「深度优先搜索」来遍历解空间。在二叉树章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。下面,我们将从前序遍历入手,逐步了解回溯算法的工作原理。
|
||||
|
||||
!!! question "例题一"
|
||||
|
||||
|
@ -1370,6 +1370,15 @@ comments: true
|
|||
- 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
|
||||
- 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。
|
||||
|
||||
请注意,回溯算法通常不是解决组合优化问题的最优方法。0-1 背包问题通常使用动态规划解决;旅行商是一个 NP-Hard 问题,常见的算法有遗传算法和蚁群算法等;最大团问题是图轮中的一个经典 NP-Hard 问题,通常用贪心算法等启发式算法来解决。
|
||||
请注意,回溯算法通常不是解决组合优化问题的最优方法。0-1 背包问题通常使用动态规划解决;旅行商是一个 NP-Hard 问题,常用解决方法有遗传算法和蚁群算法等;最大团问题是图轮中的一个经典 NP-Hard 问题,通常用贪心算法等启发式算法来解决。
|
||||
|
||||
在接下来的章节中,我们将一起探讨几个经典的回溯算法问题:全排列、子集和、$n$ 皇后。
|
||||
## 12.1.6. 优势与局限性
|
||||
|
||||
回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优势在于它能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。
|
||||
|
||||
然而,在处理大规模或者复杂问题时,**回溯算法的运行效率可能难以接受**。这是因为在最坏的情况下,回溯算法需要遍历解空间的所有可能解。例如,求解 $n$ 皇后问题的时间复杂度可以达到 $O(n!)$ 。回溯算法的空间复杂度也可能较高。因为在每一次递归调用时,都需要保存当前的状态(例如选择路径、用于剪枝的辅助变量等),对于深度很大的递归,空间需求可能会变得非常大。
|
||||
|
||||
即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于我们无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何进行效率优化**:
|
||||
|
||||
- 上文介绍过的剪枝是一种常用的优化方法。它可以避免搜索那些肯定不会产生有效解的路径,从而节省时间和空间。
|
||||
- 另一个常用的优化方法是加入「启发式搜索 Heuristic Search」策略,它在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。
|
||||
|
|
|
@ -10,11 +10,11 @@ comments: true
|
|||
|
||||
例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,由于集合中的数字可以被重复选取,因此解为 $\{3, 3, 3\}, \{4, 5\}$ 。请注意,子集是不区分元素顺序的,例如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一个子集。
|
||||
|
||||
## 12.3.1. 参考全排列解法
|
||||
## 12.3.1. 从全排列引出解法
|
||||
|
||||
从回溯算法的角度看,我们可以把子集的生成过程想象成一系列选择的结果,并在选择过程中实时更新“元素和”,当元素和等于 `target` 时,就将子集记录至结果列表。
|
||||
类似于上节全排列问题的解法,我们可以把子集的生成过程想象成一系列选择的结果,并在选择过程中实时更新“元素和”,当元素和等于 `target` 时,就将子集记录至结果列表。
|
||||
|
||||
与上节全排列问题不同的是,本题允许重复选取同一元素,因此无需借助 `selected` 布尔列表来记录元素是否已被选择。我们可以对全排列代码进行小幅修改,初步得到解题代码。
|
||||
而与全排列问题不同的是,本题允许重复选取同一元素,因此无需借助 `selected` 布尔列表来记录元素是否已被选择。我们可以对全排列代码进行小幅修改,初步得到解题代码。
|
||||
|
||||
=== "Java"
|
||||
|
||||
|
@ -396,7 +396,7 @@ comments: true
|
|||
|
||||
<p align="center"> Fig. 子集和 I 回溯过程 </p>
|
||||
|
||||
## 12.3.3. 考虑相等元素
|
||||
## 12.3.3. 相等元素剪枝
|
||||
|
||||
!!! question
|
||||
|
||||
|
|
16
chapter_backtracking/summary.md
Normal file
16
chapter_backtracking/summary.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
comments: true
|
||||
---
|
||||
|
||||
# 12.5. 小结
|
||||
|
||||
- 回溯算法本质是穷举法,通过对解空间进行深度优先遍历来寻找符合条件的解。在搜索过程中,遇到满足条件的解则记录,直至找到所有解或遍历完成后结束。
|
||||
- 回溯算法的搜索过程包括尝试与回退两个部分。它通过深度优先搜索来尝试各种选择,当遇到不满足约束条件的情况时,则撤销上一步的选择,退回到之前的状态,并继续尝试其他选择。尝试与回退是两个方向相反的操作。
|
||||
- 回溯问题通常包含多个约束条件,它们可用于实现剪枝操作。剪枝可以提前结束不必要的搜索分支,大幅提升搜索效率。
|
||||
- 回溯算法主要可用于解决搜索问题和约束满足问题。组合优化问题虽然可以用回溯算法解决,但往往存在更高效率或更好效果的解法。
|
||||
- 全排列问题旨在搜索给定集合的所有可能的排列。我们借助一个数组来记录每个元素是否被选择,剪枝掉重复选择同一元素的搜索分支,确保每个元素只被选择一次。
|
||||
- 在全排列问题中,如果集合中存在重复元素,则最终结果会出现重复排列。我们需要约束相等元素在每轮中只能被选择一次,这通常借助一个哈希表来实现。
|
||||
- 子集和问题的目标是在给定集合中找到和为目标值的所有子集。集合不区分元素顺序,而搜索过程会输出所有顺序的结果,产生重复子集。我们在回溯前将数据进行排序,并设置一个变量来指示每一轮的遍历起点,从而将生成重复子集的搜索分支进行剪枝。
|
||||
- 对于子集和问题,数组中的相等元素会产生重复集合。我们利用数组已排序的前置条件,通过判断相邻元素是否相等实现剪枝,从而确保相等元素在每轮中只能被选中一次。
|
||||
- $n$ 皇后旨在寻找将 $n$ 个皇后放置到 $n \times n$ 尺寸棋盘上的方案,要求所有皇后两两之间无法攻击对方。该问题的约束条件有行约束、列约束、主对角线和副对角线约束。为满足行约束,我们采用按行放置的策略,保证每一行放置一个皇后。
|
||||
- 列约束和对角线约束的处理方式类似。对于列约束,我们利用一个数组来记录每一列是否有皇后,从而指示选中的格子是否合法。对于对角线约束,我们借助两个数组来分别记录该主、副对角线是否存在皇后;难点在于找处在到同一主(副)对角线上格子满足的行列索引规律。
|
2
index.md
2
index.md
|
@ -13,7 +13,7 @@ hide:
|
|||
|
||||
<h2 align="center"> 《 Hello 算法 》</h2>
|
||||
|
||||
<p align="center"> 在动画与代码中掌握数据结构与算法 </p>
|
||||
<p align="center"> 动画图解、一键运行的数据结构与算法教程 </p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/krahets/hello-algo">
|
||||
|
|
Loading…
Reference in a new issue