mirror of
https://github.com/krahets/hello-algo.git
synced 2025-11-03 05:27:55 +08:00
Release Rust code to documents. (#656)
This commit is contained in:
@ -102,6 +102,12 @@
|
||||
[class]{}-[func]{bubbleSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="bubble_sort.rs"
|
||||
[class]{}-[func]{bubble_sort}
|
||||
```
|
||||
|
||||
## 效率优化
|
||||
|
||||
我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。
|
||||
@ -174,6 +180,12 @@
|
||||
[class]{}-[func]{bubbleSortWithFlag}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="bubble_sort.rs"
|
||||
[class]{}-[func]{bubble_sort_with_flag}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度为 $O(n^2)$ 、自适应排序** :各轮“冒泡”遍历的数组长度依次为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ ,总和为 $\frac{(n - 1) n}{2}$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。
|
||||
|
||||
@ -80,6 +80,12 @@
|
||||
[class]{}-[func]{bucketSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="bucket_sort.rs"
|
||||
[class]{}-[func]{bucket_sort}
|
||||
```
|
||||
|
||||
!!! question "桶排序的适用场景是什么?"
|
||||
|
||||
桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。
|
||||
|
||||
@ -78,6 +78,12 @@
|
||||
[class]{}-[func]{countingSortNaive}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="counting_sort.rs"
|
||||
[class]{}-[func]{counting_sort_naive}
|
||||
```
|
||||
|
||||
!!! note "计数排序与桶排序的联系"
|
||||
|
||||
从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作是将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。
|
||||
@ -191,6 +197,12 @@ $$
|
||||
[class]{}-[func]{countingSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="counting_sort.rs"
|
||||
[class]{}-[func]{counting_sort}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度 $O(n + m)$** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。
|
||||
|
||||
@ -148,6 +148,14 @@
|
||||
[class]{}-[func]{heapSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="heap_sort.rs"
|
||||
[class]{}-[func]{sift_down}
|
||||
|
||||
[class]{}-[func]{heap_sort}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度 $O(n \log n)$ 、非自适应排序** :建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。
|
||||
|
||||
@ -85,6 +85,12 @@
|
||||
[class]{}-[func]{insertionSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="insertion_sort.rs"
|
||||
[class]{}-[func]{insertion_sort}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度 $O(n^2)$ 、自适应排序** :最差情况下,每次插入操作分别需要循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和得到 $\frac{(n - 1) n}{2}$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。
|
||||
|
||||
@ -139,6 +139,14 @@
|
||||
[class]{}-[func]{mergeSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="merge_sort.rs"
|
||||
[class]{}-[func]{merge}
|
||||
|
||||
[class]{}-[func]{merge_sort}
|
||||
```
|
||||
|
||||
合并方法 `merge()` 代码中的难点包括:
|
||||
|
||||
- **在阅读代码时,需要特别注意各个变量的含义**。`nums` 的待合并区间为 `[left, right]` ,但由于 `tmp` 仅复制了 `nums` 该区间的元素,因此 `tmp` 对应区间为 `[0, right - left]` 。
|
||||
|
||||
@ -125,6 +125,12 @@
|
||||
[class]{QuickSort}-[func]{_partition}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="quick_sort.rs"
|
||||
[class]{QuickSort}-[func]{partition}
|
||||
```
|
||||
|
||||
## 算法流程
|
||||
|
||||
1. 首先,对原数组执行一次「哨兵划分」,得到未排序的左子数组和右子数组。
|
||||
@ -199,6 +205,12 @@
|
||||
[class]{QuickSort}-[func]{quickSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="quick_sort.rs"
|
||||
[class]{QuickSort}-[func]{quick_sort}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度 $O(n \log n)$ 、自适应排序** :在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。
|
||||
@ -311,6 +323,14 @@
|
||||
[class]{QuickSortMedian}-[func]{_partition}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="quick_sort.rs"
|
||||
[class]{QuickSortMedian}-[func]{median_three}
|
||||
|
||||
[class]{QuickSortMedian}-[func]{partition}
|
||||
```
|
||||
|
||||
## 尾递归优化
|
||||
|
||||
**在某些输入下,快速排序可能占用空间较多**。以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 $0$ ,递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。
|
||||
@ -382,3 +402,9 @@
|
||||
```dart title="quick_sort.dart"
|
||||
[class]{QuickSortTailCall}-[func]{quickSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="quick_sort.rs"
|
||||
[class]{QuickSortTailCall}-[func]{quick_sort}
|
||||
```
|
||||
|
||||
@ -134,6 +134,16 @@ $$
|
||||
[class]{}-[func]{radixSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="radix_sort.rs"
|
||||
[class]{}-[func]{digit}
|
||||
|
||||
[class]{}-[func]{counting_sort_digit}
|
||||
|
||||
[class]{}-[func]{radix_sort}
|
||||
```
|
||||
|
||||
!!! question "为什么从最低位开始排序?"
|
||||
|
||||
在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,我们应该先排序低位再排序高位。
|
||||
|
||||
@ -111,6 +111,12 @@
|
||||
[class]{}-[func]{selectionSort}
|
||||
```
|
||||
|
||||
=== "Rust"
|
||||
|
||||
```rust title="selection_sort.rs"
|
||||
[class]{}-[func]{selection_sort}
|
||||
```
|
||||
|
||||
## 算法特性
|
||||
|
||||
- **时间复杂度为 $O(n^2)$ 、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$ , $n - 1$ , $\cdots$ , $2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。
|
||||
|
||||
Reference in New Issue
Block a user