算法与数据结构面试宝典——回溯算法详解(一)

回溯算法

「回溯算法 backtracking algorithm」是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。

回溯算法通常采用“深度优先搜索”来遍历解空间。在二叉树章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。

!!! question “例题一”

给定一个二叉树,搜索并记录所有值为 $7$ 的节点,请返回节点列表。

对于此题,我们前序遍历这颗树,并判断当前节点的值是否为 7 7 7 ,若是则将该节点的值加入到结果列表 res 之中。相关过程实现如下图和以下代码所示。

=== “Python”

```python title="preorder_traversal_i_compact.py"
[class]{}-[func]{pre_order}
```

=== “C++”

```cpp title="preorder_traversal_i_compact.cpp"
[class]{}-[func]{preOrder}
```

=== “Java”

```java title="preorder_traversal_i_compact.java"
[class]{preorder_traversal_i_compact}-[func]{preOrder}
```

=== “C#”

```csharp title="preorder_traversal_i_compact.cs"
[class]{preorder_traversal_i_compact}-[func]{preOrder}
```

=== “Go”

```go title="preorder_traversal_i_compact.go"
[class]{}-[func]{preOrderI}
```

=== “Swift”

```swift title="preorder_traversal_i_compact.swift"
[class]{}-[func]{preOrder}
```

=== “JS”

```javascript title="preorder_traversal_i_compact.js"
[class]{}-[func]{preOrder}
```

=== “TS”

```typescript title="preorder_traversal_i_compact.ts"
[class]{}-[func]{preOrder}
```

=== “Dart”

```dart title="preorder_traversal_i_compact.dart"
[class]{}-[func]{preOrder}
```

=== “Rust”

```rust title="preorder_traversal_i_compact.rs"
[class]{}-[func]{pre_order}
```

=== “C”

```c title="preorder_traversal_i_compact.c"
[class]{}-[func]{preOrder}
```

=== “Zig”

```zig title="preorder_traversal_i_compact.zig"
[class]{}-[func]{preOrder}
```

Insert image description here

尝试与回退

之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。

对于例题一,访问每个节点都代表一次“尝试”,而越过叶结点或返回父节点的 return 则表示“回退”。

值得说明的是,回退并不仅仅包括函数返回。为解释这一点,我们对例题一稍作拓展。

!!! question “例题二”

在二叉树中搜索所有值为 $7$ 的节点,**请返回根节点到这些节点的路径**。

在例题一代码的基础上,我们需要借助一个列表 path 记录访问过的节点路径。当访问到值为 7 7 When the node is 7 , copy itpathand add it to the result listres. After the traversal is completed,resall solutions are saved in .

=== “Python”

```python title="preorder_traversal_ii_compact.py"
[class]{}-[func]{pre_order}
```

=== “C++”

```cpp title="preorder_traversal_ii_compact.cpp"
[class]{}-[func]{preOrder}
```

=== “Java”

```java title="preorder_traversal_ii_compact.java"
[class]{preorder_traversal_ii_compact}-[func]{preOrder}
```

=== “C#”

```csharp title="preorder_traversal_ii_compact.cs"
[class]{preorder_traversal_ii_compact}-[func]{preOrder}
```

=== “Go”

```go title="preorder_traversal_ii_compact.go"
[class]{}-[func]{preOrderII}
```

=== “Swift”

```swift title="preorder_traversal_ii_compact.swift"
[class]{}-[func]{preOrder}
```

=== “JS”

```javascript title="preorder_traversal_ii_compact.js"
[class]{}-[func]{preOrder}
```

=== “TS”

```typescript title="preorder_traversal_ii_compact.ts"
[class]{}-[func]{preOrder}
```

=== “Dart”

```dart title="preorder_traversal_ii_compact.dart"
[class]{}-[func]{preOrder}
```

=== “Rust”

```rust title="preorder_traversal_ii_compact.rs"
[class]{}-[func]{pre_order}
```

=== “C”

```c title="preorder_traversal_ii_compact.c"
[class]{}-[func]{preOrder}
```

=== “Zig”

```zig title="preorder_traversal_ii_compact.zig"
[class]{}-[func]{preOrder}
```

In each "try", we record the path by pathadding ; before "rolling back", we need to pathpop the node out to restore the state before this attempt .

Observing the process shown in the figure below, we can understand try and rollback as "forward" and "undo" . The two operations are inverse to each other.

=== “<1>”
Insert image description here

=== “<2>”
Insert image description here

=== “<3>”
Insert image description here

=== “<4>”
Insert image description here

=== “<5>”
Insert image description here

=== “<6>”
Insert image description here

=== “<7>”
Insert image description here

=== “<8>”
Insert image description here

=== “<9>”
Insert image description here

=== “<10>”
Insert image description here

=== “<11>”
Insert image description here

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132865295