Add Dart codes to the documents. (#529)

This commit is contained in:
Yudong Jin
2023-06-02 02:40:26 +08:00
committed by GitHub
parent 041a989d33
commit 025051c81b
38 changed files with 849 additions and 96 deletions

View File

@ -96,6 +96,12 @@
[class]{}-[func]{bubbleSort}
```
=== "Dart"
```dart title="bubble_sort.dart"
[class]{}-[func]{bubbleSort}
```
## 效率优化
我们发现,如果某轮“冒泡”中没有执行任何交换操作,说明数组已经完成排序,可直接返回结果。因此,可以增加一个标志位 `flag` 来监测这种情况,一旦出现就立即返回。
@ -162,6 +168,12 @@
[class]{}-[func]{bubbleSortWithFlag}
```
=== "Dart"
```dart title="bubble_sort.dart"
[class]{}-[func]{bubbleSortWithFlag}
```
## 算法特性
- **时间复杂度为 $O(n^2)$ 、自适应排序** :各轮“冒泡”遍历的数组长度依次为 $n - 1$ , $n - 2$ , $\cdots$ , $2$ , $1$ ,总和为 $\frac{(n - 1) n}{2}$ 。在引入 `flag` 优化后,最佳时间复杂度可达到 $O(n)$ 。

View File

@ -74,6 +74,12 @@
[class]{}-[func]{bucketSort}
```
=== "Dart"
```dart title="bucket_sort.dart"
[class]{}-[func]{bucketSort}
```
!!! question "桶排序的适用场景是什么?"
桶排序适用于处理体量很大的数据。例如,输入数据包含 100 万个元素,由于空间限制,系统内存无法一次性加载所有数据。此时,可以将数据分成 1000 个桶,然后分别对每个桶进行排序,最后将结果合并。

View File

@ -72,6 +72,12 @@
[class]{}-[func]{countingSortNaive}
```
=== "Dart"
```dart title="counting_sort.dart"
[class]{}-[func]{countingSortNaive}
```
!!! note "计数排序与桶排序的联系"
从桶排序的角度看,我们可以将计数排序中的计数数组 `counter` 的每个索引视为一个桶,将统计数量的过程看作是将各个元素分配到对应的桶中。本质上,计数排序是桶排序在整型数据下的一个特例。
@ -179,6 +185,12 @@ $$
[class]{}-[func]{countingSort}
```
=== "Dart"
```dart title="counting_sort.dart"
[class]{}-[func]{countingSort}
```
## 算法特性
- **时间复杂度 $O(n + m)$** :涉及遍历 `nums` 和遍历 `counter` ,都使用线性时间。一般情况下 $n \gg m$ ,时间复杂度趋于 $O(n)$ 。

View File

@ -140,6 +140,14 @@
[class]{}-[func]{heapSort}
```
=== "Dart"
```dart title="heap_sort.dart"
[class]{}-[func]{siftDown}
[class]{}-[func]{heapSort}
```
## 算法特性
- **时间复杂度 $O(n \log n)$ 、非自适应排序** :建堆操作使用 $O(n)$ 时间。从堆中提取最大元素的时间复杂度为 $O(\log n)$ ,共循环 $n - 1$ 轮。

View File

@ -79,6 +79,12 @@
[class]{}-[func]{insertionSort}
```
=== "Dart"
```dart title="insertion_sort.dart"
[class]{}-[func]{insertionSort}
```
## 算法特性
- **时间复杂度 $O(n^2)$ 、自适应排序** :最差情况下,每次插入操作分别需要循环 $n - 1$ , $n-2$ , $\cdots$ , $2$ , $1$ 次,求和得到 $\frac{(n - 1) n}{2}$ ,因此时间复杂度为 $O(n^2)$ 。在遇到有序数据时,插入操作会提前终止。当输入数组完全有序时,插入排序达到最佳时间复杂度 $O(n)$ 。

View File

@ -131,6 +131,14 @@
[class]{}-[func]{mergeSort}
```
=== "Dart"
```dart title="merge_sort.dart"
[class]{}-[func]{merge}
[class]{}-[func]{mergeSort}
```
合并方法 `merge()` 代码中的难点包括:
- **在阅读代码时,需要特别注意各个变量的含义**。`nums` 的待合并区间为 `[left, right]` ,但由于 `tmp` 仅复制了 `nums` 该区间的元素,因此 `tmp` 对应区间为 `[0, right - left]` 。

View File

@ -117,6 +117,14 @@
[class]{QuickSort}-[func]{partition}
```
=== "Dart"
```dart title="quick_sort.dart"
[class]{QuickSort}-[func]{_swap}
[class]{QuickSort}-[func]{_partition}
```
## 算法流程
1. 首先,对原数组执行一次「哨兵划分」,得到未排序的左子数组和右子数组;
@ -185,6 +193,12 @@
[class]{QuickSort}-[func]{quickSort}
```
=== "Dart"
```dart title="quick_sort.dart"
[class]{QuickSort}-[func]{quickSort}
```
## 算法特性
- **时间复杂度 $O(n \log n)$ 、自适应排序** :在平均情况下,哨兵划分的递归层数为 $\log n$ ,每层中的总循环数为 $n$ ,总体使用 $O(n \log n)$ 时间。在最差情况下,每轮哨兵划分操作都将长度为 $n$ 的数组划分为长度为 $0$ 和 $n - 1$ 的两个子数组,此时递归层数达到 $n$ 层,每层中的循环数为 $n$ ,总体使用 $O(n^2)$ 时间。
@ -289,6 +303,14 @@
[class]{QuickSortMedian}-[func]{partition}
```
=== "Dart"
```dart title="quick_sort.dart"
[class]{QuickSortMedian}-[func]{_medianThree}
[class]{QuickSortMedian}-[func]{_partition}
```
## 尾递归优化
**在某些输入下,快速排序可能占用空间较多**。以完全倒序的输入数组为例,由于每轮哨兵划分后右子数组长度为 $0$ ,递归树的高度会达到 $n - 1$ ,此时需要占用 $O(n)$ 大小的栈帧空间。
@ -354,3 +376,9 @@
```zig title="quick_sort.zig"
[class]{QuickSortTailCall}-[func]{quickSort}
```
=== "Dart"
```dart title="quick_sort.dart"
[class]{QuickSortTailCall}-[func]{quickSort}
```

View File

@ -124,6 +124,16 @@ $$
[class]{}-[func]{radixSort}
```
=== "Dart"
```dart title="radix_sort.dart"
[class]{}-[func]{digit}
[class]{}-[func]{countingSortDigit}
[class]{}-[func]{radixSort}
```
!!! question "为什么从最低位开始排序?"
在连续的排序轮次中,后一轮排序会覆盖前一轮排序的结果。举例来说,如果第一轮排序结果 $a < b$ ,而第二轮排序结果 $a > b$ ,那么第二轮的结果将取代第一轮的结果。由于数字的高位优先级高于低位,我们应该先排序低位再排序高位。

View File

@ -105,6 +105,12 @@
[class]{}-[func]{selectionSort}
```
=== "Dart"
```dart title="selection_sort.dart"
[class]{}-[func]{selectionSort}
```
## 算法特性
- **时间复杂度为 $O(n^2)$ 、非自适应排序**:外循环共 $n - 1$ 轮,第一轮的未排序区间长度为 $n$ ,最后一轮的未排序区间长度为 $2$ ,即各轮外循环分别包含 $n$ , $n - 1$ , $\cdots$ , $2$ 轮内循环,求和为 $\frac{(n - 1)(n + 2)}{2}$ 。