mirror of
https://github.com/krahets/hello-algo.git
synced 2025-12-19 07:17:54 +08:00
Replace ":" with "。"
This commit is contained in:
@@ -793,7 +793,7 @@
|
||||
- **时间**:回溯算法通常需要遍历状态空间的所有可能,时间复杂度可以达到指数阶或阶乘阶。
|
||||
- **空间**:在递归调用中需要保存当前的状态(例如路径、用于剪枝的辅助变量等),当深度很大时,空间需求可能会变得很大。
|
||||
|
||||
即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何进行效率优化**,常见方法有:
|
||||
即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何进行效率优化**,常见的效率优化方法有两种。
|
||||
|
||||
- **剪枝**:避免搜索那些肯定不会产生解的路径,从而节省时间和空间。
|
||||
- **启发式搜索**:在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。
|
||||
@@ -820,7 +820,7 @@
|
||||
- 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
|
||||
- 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。
|
||||
|
||||
请注意,对于许多组合优化问题,回溯都不是最优解决方案,例如:
|
||||
请注意,对于许多组合优化问题,回溯都不是最优解决方案。
|
||||
|
||||
- 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率。
|
||||
- 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等。
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
### 重复选择剪枝
|
||||
|
||||
为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被选择。剪枝的实现原理为:
|
||||
为了实现每个元素只被选择一次,我们考虑引入一个布尔型数组 `selected` ,其中 `selected[i]` 表示 `choices[i]` 是否已被选择,并基于它实现以下剪枝操作。
|
||||
|
||||
- 在做出选择 `choice[i]` 后,我们就将 `selected[i]` 赋值为 $\text{True}$ ,代表它已被选择。
|
||||
- 遍历选择列表 `choices` 时,跳过所有已被选择过的节点,即剪枝。
|
||||
@@ -269,7 +269,7 @@
|
||||
|
||||
### 两种剪枝对比
|
||||
|
||||
请注意,虽然 `selected` 和 `duplicated` 都用作剪枝,但两者的目标不同:
|
||||
请注意,虽然 `selected` 和 `duplicated` 都用作剪枝,但两者的目标是不同的。
|
||||
|
||||
- **重复选择剪枝**:整个搜索过程中只有一个 `selected` 。它记录的是当前状态中包含哪些元素,作用是避免某个元素在 `state` 中重复出现。
|
||||
- **相等元素剪枝**:每轮选择(即每个开启的 `backtrack` 函数)都包含一个 `duplicated` 。它记录的是在遍历中哪些元素已被选择过,作用是保证相等元素只被选择一次。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
给定一个正整数数组 `nums` 和一个目标正整数 `target` ,请找出所有可能的组合,使得组合中的元素和等于 `target` 。给定数组无重复元素,每个元素可以被选取多次。请以列表形式返回这些组合,列表中不应包含重复组合。
|
||||
|
||||
例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,解为 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意两点:
|
||||
例如,输入集合 $\{3, 4, 5\}$ 和目标整数 $9$ ,解为 $\{3, 3, 3\}, \{4, 5\}$ 。需要注意以下两点。
|
||||
|
||||
- 输入集合中的元素可以被无限次重复选取。
|
||||
- 子集是不区分元素顺序的,比如 $\{4, 5\}$ 和 $\{5, 4\}$ 是同一个子集。
|
||||
@@ -119,19 +119,19 @@
|
||||
|
||||

|
||||
|
||||
为了去除重复子集,**一种直接的思路是对结果列表进行去重**。但这个方法效率很低,因为:
|
||||
为了去除重复子集,**一种直接的思路是对结果列表进行去重**。但这个方法效率很低,有两方面原因。
|
||||
|
||||
- 当数组元素较多,尤其是当 `target` 较大时,搜索过程会产生大量的重复子集。
|
||||
- 比较子集(数组)的异同非常耗时,需要先排序数组,再比较数组中每个元素的异同。
|
||||
|
||||
### 重复子集剪枝
|
||||
|
||||
**我们考虑在搜索过程中通过剪枝进行去重**。观察下图,重复子集是在以不同顺序选择数组元素时产生的,具体来看:
|
||||
**我们考虑在搜索过程中通过剪枝进行去重**。观察下图,重复子集是在以不同顺序选择数组元素时产生的,例如以下情况。
|
||||
|
||||
1. 第一轮和第二轮分别选择 $3$ , $4$ ,会生成包含这两个元素的所有子集,记为 $[3, 4, \dots]$ 。
|
||||
2. 若第一轮选择 $4$ ,**则第二轮应该跳过 $3$** ,因为该选择产生的子集 $[4, 3, \dots]$ 和 `1.` 中生成的子集完全重复。
|
||||
1. 当第一轮和第二轮分别选择 $3$ , $4$ 时,会生成包含这两个元素的所有子集,记为 $[3, 4, \dots]$ 。
|
||||
2. 之后,当第一轮选择 $4$ 时,**则第二轮应该跳过 $3$** ,因为该选择产生的子集 $[4, 3, \dots]$ 和 `1.` 中生成的子集完全重复。
|
||||
|
||||
如下图所示,每一层的选择都是从左到右被逐个尝试的,因此越靠右剪枝越多。
|
||||
在搜索中,每一层的选择都是从左到右被逐个尝试的,因此越靠右的分支被剪掉的越多。
|
||||
|
||||
1. 前两轮选择 $3$ , $5$ ,生成子集 $[3, 5, \dots]$ 。
|
||||
2. 前两轮选择 $4$ , $5$ ,生成子集 $[4, 5, \dots]$ 。
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
为实现该剪枝,我们初始化变量 `start` ,用于指示遍历起点。**当做出选择 $x_{i}$ 后,设定下一轮从索引 $i$ 开始遍历**。这样做就可以让选择序列满足 $i_1 \leq i_2 \leq \dots \leq i_m$ ,从而保证子集唯一。
|
||||
|
||||
除此之外,我们还对代码进行了两项优化:
|
||||
除此之外,我们还对代码进行了以下两项优化。
|
||||
|
||||
- 在开启搜索前,先将数组 `nums` 排序。在遍历所有选择时,**当子集和超过 `target` 时直接结束循环**,因为后边的元素更大,其子集和都一定会超过 `target` 。
|
||||
- 省去元素和变量 `total`,**通过在 `target` 上执行减法来统计元素和**,当 `target` 等于 $0$ 时记录解。
|
||||
|
||||
Reference in New Issue
Block a user