This commit is contained in:
krahets 2023-04-16 04:55:51 +08:00
parent 72a8318c75
commit dede472c45
5 changed files with 654 additions and 21 deletions

View File

@ -2,7 +2,7 @@
comments: true
---
# 12.2.   一起参与创作
# 13.2.   一起参与创作
!!! success "开源的魅力"
@ -10,7 +10,7 @@ comments: true
由于作者能力有限,书中难免存在一些遗漏和错误,请您谅解。如果您发现了笔误、失效链接、内容缺失、文字歧义、解释不清晰或行文结构不合理等问题,请协助我们进行修正,以帮助其他读者获得更优质的学习资源。所有[撰稿人](https://github.com/krahets/hello-algo/graphs/contributors)将在仓库和网站主页上展示,以感谢他们对开源社区的无私奉献!
## 12.2.1.   内容微调
## 13.2.1.   内容微调
在每个页面的右上角有一个「编辑」图标,您可以按照以下步骤修改文本或代码:
@ -24,7 +24,7 @@ comments: true
由于图片无法直接修改,因此需要通过新建 [Issue](https://github.com/krahets/hello-algo/issues) 或评论留言来描述图片问题,我们会尽快重新绘制并替换图片。
## 12.2.2.   内容创作
## 13.2.2.   内容创作
如果您有兴趣参与此开源项目,包括将代码翻译成其他编程语言、扩展文章内容等,那么需要实施 Pull Request 工作流程:
@ -34,7 +34,7 @@ comments: true
4. 将本地所做更改 Commit ,然后 Push 至远程仓库;
5. 刷新仓库网页点击“Create pull request”按钮即可发起拉取请求
## 12.2.3.   Docker 部署
## 13.2.3.   Docker 部署
我们可以通过 Docker 来部署本项目。执行以下脚本,稍等片刻后,即可使用浏览器打开 `http://localhost:8000` 来访问本项目。

View File

@ -2,49 +2,49 @@
comments: true
---
# 12.1.   编程环境安装
# 13.1.   编程环境安装
## 12.1.1.   安装 VSCode
## 13.1.1.   安装 VSCode
本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 [VSCode](https://code.visualstudio.com/) 。
## 12.1.2.   Java 环境
## 13.1.2.   Java 环境
1. 下载并安装 [OpenJDK](https://jdk.java.net/18/)(版本需满足 > JDK 9
2. 在 VSCode 的插件市场中搜索 `java` ,安装 Java Extension Pack 。
## 12.1.3.   C/C++ 环境
## 13.1.3.   C/C++ 环境
1. Windows 系统需要安装 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)[配置教程](https://blog.csdn.net/qq_33698226/article/details/129031241)MacOS 自带 Clang 无需安装。
2. 在 VSCode 的插件市场中搜索 `c++` ,安装 C/C++ Extension Pack 。
## 12.1.4.   Python 环境
## 13.1.4.   Python 环境
1. 下载并安装 [Miniconda3](https://docs.conda.io/en/latest/miniconda.html) 。
2. 在 VSCode 的插件市场中搜索 `python` ,安装 Python Extension Pack 。
## 12.1.5.   Go 环境
## 13.1.5.   Go 环境
1. 下载并安装 [go](https://go.dev/dl/) 。
2. 在 VSCode 的插件市场中搜索 `go` ,安装 Go 。
3. 快捷键 `Ctrl + Shift + P` 呼出命令栏,输入 go ,选择 `Go: Install/Update Tools` ,全部勾选并安装即可。
## 12.1.6.   JavaScript 环境
## 13.1.6.   JavaScript 环境
1. 下载并安装 [node.js](https://nodejs.org/en/) 。
2. 在 VSCode 的插件市场中搜索 `javascript` ,安装 JavaScript (ES6) code snippets 。
## 12.1.7.   C# 环境
## 13.1.7.   C# 环境
1. 下载并安装 [.Net 6.0](https://dotnet.microsoft.com/en-us/download)
2. 在 VSCode 的插件市场中搜索 `c#` ,安装 c# 。
## 12.1.8.   Swift 环境
## 13.1.8.   Swift 环境
1. 下载并安装 [Swift](https://www.swift.org/download/)
2. 在 VSCode 的插件市场中搜索 `swift` ,安装 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)。
## 12.1.9.   Rust 环境
## 13.1.9.   Rust 环境
1. 下载并安装 [Rust](https://www.rust-lang.org/tools/install)
2. 在 VSCode 的插件市场中搜索 `rust` ,安装 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)。

View File

@ -0,0 +1,625 @@
---
comments: true
---
# 12.1.   回溯算法
「回溯算法 Backtracking Algorithm」是一种通过穷举来解决问题的方法它的核心思想是从一个初始状态出发暴力搜索所有可能的解决方案当遇到正确的解则将其记录直到找到解或者尝试了所有可能的选择都无法找到解为止。
回溯算法通常采用「深度优先搜索」来遍历解空间。在二叉树章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。下面,我们从二叉树的前序遍历入手,逐步了解回溯算法的工作原理。
!!! question "例题一:在二叉树中搜索并返回所有值为 $7$ 的节点"
**解题思路**:前序遍历这颗树,并判断当前节点的值是否为 $7$ ,若是则将该节点的值加入到结果列表 `res` 之中。
=== "Java"
```java title="preorder_find_nodes.java"
/* 前序遍历 */
void preOrder(TreeNode root) {
if (root == null) {
return;
}
// 尝试
if (root.val == 7) {
// 记录解
res.add(root);
}
preOrder(root.left);
preOrder(root.right);
// 回退
return;
}
```
=== "C++"
```cpp title="preorder_find_nodes.cpp"
[class]{}-[func]{preOrder}
```
=== "Python"
```python title="preorder_find_nodes.py"
def pre_order(root: TreeNode) -> None:
"""前序遍历"""
if root is None:
return
if root.val == 7:
# 记录解
res.append(root)
pre_order(root.left)
pre_order(root.right)
```
=== "Go"
```go title="preorder_find_nodes.go"
[class]{}-[func]{preOrder}
```
=== "JavaScript"
```javascript title="preorder_find_nodes.js"
[class]{}-[func]{preOrder}
```
=== "TypeScript"
```typescript title="preorder_find_nodes.ts"
[class]{}-[func]{preOrder}
```
=== "C"
```c title="preorder_find_nodes.c"
[class]{}-[func]{preOrder}
```
=== "C#"
```csharp title="preorder_find_nodes.cs"
[class]{preorder_find_nodes}-[func]{preOrder}
```
=== "Swift"
```swift title="preorder_find_nodes.swift"
[class]{}-[func]{preOrder}
```
=== "Zig"
```zig title="preorder_find_nodes.zig"
[class]{}-[func]{preOrder}
```
![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png)
<p align="center"> Fig. 在前序遍历中搜索节点 </p>
## 12.1.1. &nbsp; 尝试与回退
**之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。
对于例题一,访问每个节点都代表一次“尝试”,而越过叶结点或返回父节点的 `return` 则表示“回退”。
值得说明的是,**回退并不等价于函数返回**。为解释这一点,我们对例题一稍作拓展。
!!! question "在二叉树中搜索所有值为 $7$ 的节点,**返回根节点到这些节点的路径**"
**解题思路**:在例题一代码的基础上,我们需要借助一个列表 `path` 记录访问过的节点路径。当访问到值为 $7$ 的节点时,则复制 `path` 并添加进结果列表 `res` 。遍历完成后,`res` 中保存的就是所有的解。
=== "Java"
```java title="preorder_find_paths.java"
/* 前序遍历 */
void preOrder(TreeNode root) {
if (root == null) {
return;
}
// 尝试
path.add(root);
if (root.val == 7) {
// 记录解
res.add(new ArrayList<>(path));
}
preOrder(root.left);
preOrder(root.right);
// 回退
path.remove(path.size() - 1);
}
```
=== "C++"
```cpp title="preorder_find_paths.cpp"
[class]{}-[func]{preOrder}
```
=== "Python"
```python title="preorder_find_paths.py"
def pre_order(root: TreeNode) -> None:
"""前序遍历"""
if root is None:
return
# 尝试
path.append(root)
if root.val == 7:
# 记录解
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# 回退
path.pop()
return
```
=== "Go"
```go title="preorder_find_paths.go"
[class]{}-[func]{preOrder}
```
=== "JavaScript"
```javascript title="preorder_find_paths.js"
[class]{}-[func]{preOrder}
```
=== "TypeScript"
```typescript title="preorder_find_paths.ts"
[class]{}-[func]{preOrder}
```
=== "C"
```c title="preorder_find_paths.c"
[class]{}-[func]{preOrder}
```
=== "C#"
```csharp title="preorder_find_paths.cs"
[class]{preorder_find_paths}-[func]{preOrder}
```
=== "Swift"
```swift title="preorder_find_paths.swift"
[class]{}-[func]{preOrder}
```
=== "Zig"
```zig title="preorder_find_paths.zig"
[class]{}-[func]{preOrder}
```
在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。换句话说,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为相反的。
=== "<1>"
![preorder_find_paths_step1](backtracking_algorithm.assets/preorder_find_paths_step1.png)
=== "<2>"
![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png)
=== "<3>"
![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png)
=== "<4>"
![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png)
=== "<5>"
![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png)
=== "<6>"
![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png)
=== "<7>"
![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png)
=== "<8>"
![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png)
=== "<9>"
![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png)
=== "<10>"
![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png)
=== "<11>"
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png)
## 12.1.2. &nbsp; 剪枝
复杂的回溯问题通常包含一个或多个约束条件,**约束条件通常可用于“剪枝”**。
!!! question "例题三:在二叉树中搜索所有值为 $7$ 的节点,返回根节点到这些节点的路径,**路径中不能包含值为 $3$ 的节点**"
**解题思路**:在例题二的基础上添加剪枝操作,当遇到值为 $3$ 的节点时,则终止继续搜索。
=== "Java"
```java title="preorder_find_constrained_paths.java"
/* 前序遍历 */
void preOrder(TreeNode root) {
// 剪枝
if (root == null || root.val == 3) {
return;
}
// 尝试
path.add(root);
if (root.val == 7) {
// 记录解
res.add(new ArrayList<>(path));
}
preOrder(root.left);
preOrder(root.right);
// 回退
path.remove(path.size() - 1);
}
```
=== "C++"
```cpp title="preorder_find_constrained_paths.cpp"
[class]{}-[func]{preOrder}
```
=== "Python"
```python title="preorder_find_constrained_paths.py"
def pre_order(root: TreeNode) -> None:
"""前序遍历"""
# 剪枝
if root is None or root.val == 3:
return
# 尝试
path.append(root)
if root.val == 7:
# 记录解
res.append(list(path))
pre_order(root.left)
pre_order(root.right)
# 回退
path.pop()
return
```
=== "Go"
```go title="preorder_find_constrained_paths.go"
[class]{}-[func]{preOrder}
```
=== "JavaScript"
```javascript title="preorder_find_constrained_paths.js"
[class]{}-[func]{preOrder}
```
=== "TypeScript"
```typescript title="preorder_find_constrained_paths.ts"
[class]{}-[func]{preOrder}
```
=== "C"
```c title="preorder_find_constrained_paths.c"
[class]{}-[func]{preOrder}
```
=== "C#"
```csharp title="preorder_find_constrained_paths.cs"
[class]{preorder_find_constrained_paths}-[func]{preOrder}
```
=== "Swift"
```swift title="preorder_find_constrained_paths.swift"
[class]{}-[func]{preOrder}
```
=== "Zig"
```zig title="preorder_find_constrained_paths.zig"
[class]{}-[func]{preOrder}
```
剪枝是一个非常形象的名词。在搜索过程中,**我们利用约束条件“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提升搜索效率。
![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
<p align="center"> Fig. 根据约束条件剪枝 </p>
## 12.1.3. &nbsp; 常用术语
为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。
| 名词 | 定义 | 例题三 |
| ------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 解 Solution | 解是满足问题特定条件的答案。回溯算法的目标是找到一个或多个满足条件的解 | 根节点到节点 $7$ 的所有路径,且路径中不包含值为 $3$ 的节点 |
| 状态 State | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 |
| 约束条件 Constraint | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 要求路径中不能包含值为 $3$ 的节点 |
| 尝试 Attempt | 尝试是在搜索过程中,根据当前状态和可用选择来探索解空间的过程。尝试包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ |
| 回退 Backtracking | 回退指在搜索中遇到到不满足约束条件或无法继续搜索的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶结点、结束结点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 |
| 剪枝 Pruning | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则终止继续搜索 |
!!! tip
解、状态、约束条件等术语是通用的,适用于回溯算法、动态规划、贪心算法等。
## 12.1.4. &nbsp; 框架代码
回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。为提升代码通用性,我们希望将回溯算法的“尝试、回退、剪枝”的主体框架提炼出来。
`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)
```
下面,我们尝试基于此框架来解决例题三。在例题三中,状态 `state` 是节点遍历路径,选择 `choices` 是当前节点的左子节点和右子节点,结果 `res` 是路径列表,实现代码如下所示。
=== "Java"
```java title="backtrack_find_constrained_paths.java"
/* 判断当前状态是否为解 */
boolean isSolution(List<TreeNode> state) {
return !state.isEmpty() && state.get(state.size() - 1).val == 7;
}
/* 记录解 */
void recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {
res.add(new ArrayList<>(state));
}
/* 判断在当前状态下,该选择是否合法 */
boolean isValid(List<TreeNode> state, TreeNode choice) {
return choice != null && choice.val != 3;
}
/* 更新状态 */
void makeChoice(List<TreeNode> state, TreeNode choice) {
state.add(choice);
}
/* 恢复状态 */
void undoChoice(List<TreeNode> state, TreeNode choice) {
state.remove(state.size() - 1);
}
/* 回溯算法 */
void backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {
// 检查是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
for (TreeNode choice : choices) {
// 剪枝:检查选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, Arrays.asList(choice.left, choice.right), res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "C++"
```cpp title="backtrack_find_constrained_paths.cpp"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "Python"
```python title="backtrack_find_constrained_paths.py"
def is_solution(state: list[TreeNode]) -> bool:
"""判断当前状态是否为解"""
return state and state[-1].val == 7
def record_solution(state: list[TreeNode], res: list[list[TreeNode]]):
"""记录解"""
res.append(list(state))
def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
"""判断在当前状态下,该选择是否合法"""
return choice is not None and choice.val != 3
def make_choice(state: list[TreeNode], choice: TreeNode):
"""更新状态"""
state.append(choice)
def undo_choice(state: list[TreeNode], choice: TreeNode):
"""恢复状态"""
state.pop()
def backtrack(state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]):
"""回溯算法"""
# 检查是否为解
if is_solution(state):
# 记录解
record_solution(state, res)
return
# 遍历所有选择
for choice in choices:
# 剪枝:检查选择是否合法
if is_valid(state, choice):
# 尝试:做出选择,更新状态
make_choice(state, choice)
backtrack(state, [choice.left, choice.right], res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
```
=== "Go"
```go title="backtrack_find_constrained_paths.go"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "JavaScript"
```javascript title="backtrack_find_constrained_paths.js"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "TypeScript"
```typescript title="backtrack_find_constrained_paths.ts"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "C"
```c title="backtrack_find_constrained_paths.c"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "C#"
```csharp title="backtrack_find_constrained_paths.cs"
[class]{backtrack_find_constrained_paths}-[func]{isSolution}
[class]{backtrack_find_constrained_paths}-[func]{recordSolution}
[class]{backtrack_find_constrained_paths}-[func]{isValid}
[class]{backtrack_find_constrained_paths}-[func]{makeChoice}
[class]{backtrack_find_constrained_paths}-[func]{undoChoice}
[class]{backtrack_find_constrained_paths}-[func]{backtrack}
```
=== "Swift"
```swift title="backtrack_find_constrained_paths.swift"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "Zig"
```zig title="backtrack_find_constrained_paths.zig"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
相较于基于前序遍历的实现代码,基于回溯算法框架的实现代码虽然显得啰嗦,但通用性更好,适用于各种不同的回溯算法问题。实际上,**所有回溯问题都可以在该框架下解决**。我们只需要根据问题特点来定义框架中的各个变量,实现各个方法即可。
## 12.1.5. &nbsp; 典型例题
**搜索问题**:这类问题的目标是找到满足特定条件的解决方案。
- 全排列问题:给定一个集合,求出其所有可能的排列组合。
- 子集和问题:给定一个集合和一个目标和,找到集合中所有和为目标和的子集。
- 汉诺塔问题:给定三个柱子和一系列大小不同的圆盘,要求将所有圆盘从一个柱子移动到另一个柱子,每次只能移动一个圆盘,且不能将大圆盘放在小圆盘上。
**约束满足问题**:这类问题的目标是找到满足所有约束条件的解。
- $n$ 皇后:在 $n \times n$ 的棋盘上放置 $n$ 个皇后,使得它们互不攻击。
- 数独:在 $9 \times 9$ 的网格中填入数字 $1$ ~ $9$ ,使得每行、每列和每个 $3 \times 3$ 子网格中的数字不重复。
- 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。
**组合优化问题**:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。
- 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。
- 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
- 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。
在接下来的章节中,我们将一起攻克几个经典的回溯算法问题。

View File

@ -536,6 +536,14 @@ $$
buckets = vector<Entry *>(100);
}
~ArrayHashMap() {
// 释放内存
for (const auto &bucket : buckets) {
delete bucket;
}
buckets.clear();
}
/* 哈希函数 */
int hashFunc(int key) {
int index = key % 100;
@ -561,7 +569,8 @@ $$
/* 删除操作 */
void remove(int key) {
int index = hashFunc(key);
// 置为 nullptr ,代表删除
// 释放内存并置为 nullptr
delete buckets[index];
buckets[index] = nullptr;
}

View File

@ -2,16 +2,15 @@
comments: true
---
# 11.1. &nbsp; 排序简介
# 11.1. &nbsp; 排序算法
「排序算法 Sorting Algorithm」使列表中的所有元素按照升序排列
「排序算法 Sorting Algorithm」用于对一组数据按照特定顺序进行排列。排序算法有着广泛的应用,因为有序数据通常能够被更有效地查找、分析和处理
- 待排序列表的元素类型可以是整数、浮点数、字符或字符串等;
- 排序算法可根据需求设定判断规则,如数字大小、字符 ASCII 码顺序或自定义规则;
在排序算法中,数据类型可以是整数、浮点数、字符或字符串等;顺序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。
![排序中不同的元素类型和判断规则](intro_to_sort.assets/sorting_examples.png)
![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png)
<p align="center"> Fig. 排序中不同的元素类型和判断规则 </p>
<p align="center"> Fig. 数据类型和判断规则示例 </p>
## 11.1.1. &nbsp; 评价维度