Skip to content

Commit

Permalink
Prepare 1.0.0b3 release
Browse files Browse the repository at this point in the history
  • Loading branch information
krahets committed May 10, 2023
1 parent ef73b7b commit def8da6
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
阅读网页版
</a>
&nbsp; | &nbsp;
<a href="https://github.com/krahets/hello-algo/releases/tag/1.0.0b2">
<a href="https://github.com/krahets/hello-algo/releases">
下载 PDF 版
</a>
</p>
Expand Down
246 changes: 227 additions & 19 deletions docs/chapter_backtracking/backtracking_algorithm.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,24 +272,232 @@

`state` 为问题的当前状态,`choices` 表示当前状态下可以做出的选择,则可得到以下回溯算法的框架代码。

```python
def backtrack(state, choices, res):
"""回溯算法框架"""
# 判断是否为解
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)
```
=== "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);
}
}
}
```

=== "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);
}
}
}
```

=== "Python"

```python title=""
def backtrack(state: State, choices: list[choice], res: list[state]) -> None:
"""回溯算法框架"""
# 判断是否为解
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)
```

=== "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)
}
}
}
```

=== "JavaScript"

```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);
}
}
}
```

=== "TypeScript"

```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);
}
}
}
```

=== "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]);
}
}
}
```

=== "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);
}
}
}
```

=== "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)
}
}
}
```

=== "Zig"

```zig title=""

```

下面,我们尝试基于此框架来解决例题三。在例题三中,状态 `state` 是节点遍历路径,选择 `choices` 是当前节点的左子节点和右子节点,结果 `res` 是路径列表,实现代码如下所示。

Expand Down Expand Up @@ -453,7 +661,7 @@ def backtrack(state, choices, res):
[class]{}-[func]{backtrack}
```

相较于基于前序遍历的实现代码,基于回溯算法框架的实现代码虽然显得啰嗦,但通用性更好,适用于各种不同的回溯算法问题。实际上,**所有回溯问题都可以在该框架下解决**我们只需要根据问题特点来定义框架中的各个变量,实现各个方法即可
相较于基于前序遍历的实现代码,基于回溯算法框架的实现代码虽然显得啰嗦,但通用性更好。实际上,**所有回溯问题都可以在该框架下解决**我们需要根据具体问题来定义 `state``choices` ,并实现框架中的各个方法

## 典型例题

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions docs/chapter_backtracking/permutations_problem.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@

<div class="center-table" markdown>

| 输入数组 | 所有排列 |
| :-------- | :--------------------------------------------------------------- |
| [1] | [1] |
| [1, 2] | [1, 2], [2, 1] |
| [1, 2, 3] | [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] |
| 输入数组 | 所有排列 |
| :---------- | :----------------------------------------------------------------- |
| $[1]$ | $[1]$ |
| $[1, 2]$ | $[1, 2], [2, 1]$ |
| $[1, 2, 3]$ | $[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]$ |

</div>

## 无重复的情况

!!! question "输入一个整数数组,数组中不包含重复元素,返回所有可能的排列。"

**从回溯算法的角度看,我们可以把生成排列的过程想象成一系列选择的结果**。假设输入数组为 `[1, 2, 3]` ,如果我们先选择 `1` 、再选择 `3` 、最后选择 `2` ,则获得排列 `[1, 3, 2]` 。回退表示撤销一个选择,之后继续尝试其他选择。
**从回溯算法的角度看,我们可以把生成排列的过程想象成一系列选择的结果**。假设输入数组为 $[1, 2, 3]$ ,如果我们先选择 $1$ 、再选择 $3$ 、最后选择 $2$ ,则获得排列 $[1, 3, 2]$ 。回退表示撤销一个选择,之后继续尝试其他选择。

从回溯算法代码的角度看,候选集合 `choices` 是输入数组中的所有元素,状态 `state` 是直至目前已被选择的元素。注意,每个元素只允许被选择一次,**因此在遍历选择时,应当排除已经选择过的元素**

Expand Down Expand Up @@ -118,13 +118,13 @@

!!! question "输入一个整数数组,**数组中可能包含重复元素**,返回所有不重复的排列。"

假设输入数组为 `[1, 1, 2]` 。为了方便区分两个重复的元素 `1` ,接下来我们将第二个元素记为 `1'` 。如下图所示,上述方法生成的排列有一半都是重复的。
假设输入数组为 $[1, 1, 2]$ 。为了方便区分两个重复的元素 $1$ ,接下来我们将第二个元素记为 $\hat{1}$ 。如下图所示,上述方法生成的排列有一半都是重复的。

![重复排列](permutations_problem.assets/permutations_ii.png)

那么,如何去除重复的排列呢?最直接地,我们可以借助一个哈希表,直接对排列结果进行去重。然而,这样做不够优雅,因为生成重复排列的搜索分支是没有必要的,应当被提前识别并剪枝,这样可以提升算法效率。

观察发现,在第一轮中,选择 `1` 或选择 `1'` 是等价的,因为在这两个选择之下生成的所有排列都是重复的。因此,我们应该把 `1'` 剪枝掉。同理,在第一轮选择 `2` 后,第二轮选择中的 `1``1'` 也会产生重复分支,因此也需要将第二轮的 `1'` 剪枝。
观察发现,在第一轮中,选择 $1$ 或选择 $\hat{1}$ 是等价的,因为在这两个选择之下生成的所有排列都是重复的。因此,我们应该把 $\hat{1}$ 剪枝掉。同理,在第一轮选择 $2$ 后,第二轮选择中的 $1$$\hat{1}$ 也会产生重复分支,因此也需要将第二轮的 $\hat{1}$ 剪枝。

![重复排列剪枝](permutations_problem.assets/permutations_ii_pruning.png)

Expand Down

0 comments on commit def8da6

Please sign in to comment.