OSDN Git Service

refactor[stl-sort.md, quick-sort.md]: 排序相关 STL
author夜轮_NachtgeistW <NachtgeistW@protonmail.com>
Mon, 21 Sep 2020 10:49:47 +0000 (18:49 +0800)
committer夜轮_NachtgeistW <NachtgeistW@protonmail.com>
Mon, 21 Sep 2020 10:49:47 +0000 (18:49 +0800)
modified:   docs/basic/quick-sort.md
modified:   docs/basic/stl-sort.md

由于内省排序在 quick-sort 中也有介绍,与 stl-sort 中原有的部分重复,因此对其进行了一些删改。

docs/basic/quick-sort.md
docs/basic/stl-sort.md

index 92fb971..595275f 100644 (file)
@@ -83,9 +83,9 @@ void quick_sort(T arr[], const int len) {
 
 ## 内省排序[^ref3]
 
-内省排序(introspective sort)是快速排序和 [堆排序](heap-sort.md) 的结合,由 David Musser 于 1997 年发明。内省排序其实是对快速排序的一种优化,保证了最差时间复杂度为 $O(n\log n)$ 。
+内省排序(英语:Introsort 或 Introspective sort)是快速排序和 [堆排序](heap-sort.md) 的结合,由 David Musser 于 1997 年发明。内省排序其实是对快速排序的一种优化,保证了最差时间复杂度为 $O(n\log n)$ 。
 
-内省排序的思路其实很简单,就是限制快速排序最大递归深度为 $\lfloor \log_2n \rfloor$ ,超过限制时就转换为堆排序。这样既保留了快速排序内存访问的局部性,又可以防止快速排序在某些情况下性能退化为 $O(n^2)$ 。
+内省排序将快速排序的最大递归深度限制为 $\lfloor \log_2n \rfloor$ ,超过限制时就转换为堆排序。这样既保留了快速排序内存访问的局部性,又可以防止快速排序在某些情况下性能退化为 $O(n^2)$ 。
 
 2000 年 6 月,SGI C++ STL 的 stl_algo.h 中 sort()函数的实现采用了内省排序算法。
 
index b594704..56e606c 100644 (file)
@@ -1,78 +1,96 @@
-## sort
+本页面将简要介绍 C 和 C++ 标准库中实现的排序算法。
 
-C 标准库实现了快速排序,即 `stdlib.h` 当中的 `qsort` 
+除已说明的函数外,本页所列函数默认定义于头文件 `<algorithm>` 中
 
-但在 OI 相关比赛当中,更为常见的库排序函数是 C++ `algorithm` 库中的 `std::sort` 函数。
+## qsort
 
-C++ 标准并未严格要求此函数的实现算法,具体实现取决于编译器。
+参见:[`qsort`](https://zh.cppreference.com/w/c/algorithm/qsort),[`std::qsort`](https://zh.cppreference.com/w/cpp/algorithm/qsort)
 
-旧版 C++ 标准中仅要求它的 **平均** 时间复杂度达到 $O(n\log n)$ ,但 C++11 标准要求它的 **最坏** 时间复杂度是达到 $O(n\log n)$ 。可以查阅 [std::sort()](https://en.cppreference.com/w/cpp/algorithm/sort) 
+该函数为 C 标准库实现的[快速排序](quick-sort.md),定义在 `<stdlib.h>` 中。在 C++ 标准库里,该函数定义在 `<cstdlib>` 中。
 
-在 [libstdc++](https://github.com/mirrors/gcc/blob/master/libstdc++-v3/include/bits/stl_algo.h) 和 [libc++](http://llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm) 中使用的都是 [Introsort](https://en.wikipedia.org/wiki/Introsort) 。
+## std::sort
 
-Introsort 限制了快速排序的分治深度,当分治达到一定深度之后,改用最坏时间复杂度为 $O(n\log n)$ 的排序算法(比如堆排序)来给子数组排序。
-
-Introsort 的这个限制使得它的最坏时间复杂度是 $O(n\log n)$ 的。
+参见:[`std::sort`](https://zh.cppreference.com/w/cpp/algorithm/sort)
 
 用法:
 
 ```cpp
 // a[0] .. a[n - 1] 为需要排序的数列
+// 对 a 原地排序,将其按从小到大的顺序排列
 std::sort(a, a + n);
-// 上面这句代码直接修改 a 数组里的元素顺序,使得现在它是从小到大排列的
 
-std::sort(a, a + n, cmp);  // cmp 为自定义的比较函数
+// cmp 为自定义的比较函数
+std::sort(a, a + n, cmp);
 ```
 
-## nth_element
+更为常见的库排序函数是 `std::sort` 函数。该函数的最后一个参数为二元比较函数,未指定 cmp 函数时,默认按从小到大的顺序排序。
+
+旧版 C++ 标准中仅要求它的 **平均** 时间复杂度达到 $O(n\log n)$ 。C++11 标准以及后续标准要求它的 **最坏** 时间复杂度达到 $O(n\log n)$ 。
+
+C++ 标准并未严格要求此函数的实现算法,具体实现取决于编译器。[libstdc++](https://github.com/mirrors/gcc/blob/master/libstdc++-v3/include/bits/stl_algo.h) 和 [libc++](http://llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm) 中的实现算法都是 [内省排序](quick-sort.md#内省排序[^ref3]) 。
 
-作用是找到选定区间内第 $k$ 大的数,并将所有比它小的数与比它大的数分别置于两侧,返回它的地址。
+## std::nth_element
 
\8e\9fç\90\86æ\98¯æ\9cªå®\8cæ\88\90ç\9a\84å¿«é\80\9fæ\8e\92åº\8fã\80\82
\8f\82è§\81ï¼\9a[`std::nth_element`](https://zh.cppreference.com/w/cpp/algorithm/nth_element)
 
 用法:
 
 ```cpp
-std::nth_element(begin, mid, end);
-std::nth_element(begin, mid, end, cmp);
+std::nth_element(first, nth, last);
+std::nth_element(first, nth, last, cmp);
 ```
 
-时间复杂度:期望 $O(n)$ 。
+它重排 $[first, last)$ 中的元素,使得 $nth$ 所指向的元素被更改为 $[first, last)$ 排好序后该位置会出现的元素。这个新的 $nth$ 元素前的所有元素小于或等于新的 $nth$ 元素后的所有元素。
+
+实现算法是未完成的内省排序。
+
+对于以上两种用法,C++ 标准要求它的平均时间复杂度为 $O(n)$ ,其中 n = `std::distance(first, last)`。
 
¸¸ç\94¨äº\8eæ\9e\84建 K-DTree
®\83常ç\94¨äº\8eæ\9e\84建 [K-D Tree](../ds/kdt.md)
 
-## stable_sort
+## std::stable_sort
 
-稳定的 $O(n\log n)$ 排序,即保证相等元素排序后的相对位置与原序列相同。
+参见:[`std::stable_sort`](https://zh.cppreference.com/w/cpp/algorithm/stable_sort)
 
-用法
+用法
 
 ```cpp
-std::stable_sort(begin, end);
-std::stable_sort(begin, end, cmp);
+std::stable_sort(first, last);
+std::stable_sort(first, last, cmp);
 ```
 
-## partial_sort
+稳定排序,保证相等元素排序后的相对位置与原序列相同。
+
+时间复杂度为 $O(n\log (n)^2)$ ,当额外内存可用时,复杂度为 $O(n\log n)$。
 
-将序列中前 $k$ 小元素按顺序置于前 $k$ 个位置,后面的元素不保证顺序。
+## std::partial_sort
 
¤\8dæ\9d\82度ï¼\9a $O(n\log k)$ 
\8f\82è§\81ï¼\9a[`std::partial_sort`](https://zh.cppreference.com/w/cpp/algorithm/partial_sort)
 
 用法:
 
 ```cpp
-std::partial_sort(begin, begin + k, end);
-std::partial_sort(begin, begin + k, end, cmp);
+std::partial_sort(first, mid, last);
+std::partial_sort(first, mid, last, cmp);
 ```
 
+将序列中前 $k$ 元素按 cmp 给定的顺序进行原地排序,后面的元素不保证顺序。未指定 cmp 函数时,默认按从小到大的顺序排序。
+
+复杂度:约 $(last-first)\log(mid-first)$ 次应用 `cmp`。
+
 原理:
 
-实现 partial_sort 的思想是:对原始容器内区间为 $[first, middle)$ 的元素执行 make_heap() 操作构造一个大根堆,然后拿 $[middle, last)$ 中的每个元素和 $first$ 进行比较, $first$ 内的元素为堆内的最大值。如果小于该最大值,则互换元素位置,并对 $[first, middle)$ 内的元素进行调整,使其保持最大堆序。比较完之后在对 $[first, middle)$ 内的元素做一次对排序 sort_heap() 操作,使其按增序排列。注意,堆序和增序是不同的。
+`std::partial_sort` 的思想是:对原始容器内区间为 $[first, mid)$ 的元素执行 `make_heap()` 操作,构造一个大根堆,然后将 $[mid, last)$ 中的每个元素和 $first$ 进行比较,保证 $first$ 内的元素为堆内的最大值。如果小于该最大值,则互换元素位置,并对 $[first, mid)$ 内的元素进行调整,使其保持最大堆序。比较完之后,再对 $[first, mid)$ 内的元素做一次对排序 `sort_heap()` 操作,使其按增序排列。注意,堆序和增序是不同的。
 
 ## 定义运算符
 
-对于内置类型(如 `int` )和用户定义的结构体,你都可以定义调用 STL 排序函数时使用的 **小于运算符** 。你可以在调用函数时同时传入一个比较运算符的函数(一般是最后一项),也可以直接重载该类型的默认运算符。参见 [cppreference](https://zh.cppreference.com/w/cpp/language/operators) 。
-下面是几个例子:
+参见: [运算符重载](https://zh.cppreference.com/w/cpp/language/operators)
+
+内置类型(如 `int` )和用户定义的结构体允许定制调用 STL 排序函数时使用的比较函数。可以在调用该函数时,在最后一个参数中传入一个实现二元比较的函数。
+
+对于用户定义的结构体,对其使用 STL 排序函数前必须定义至少一种关系运算符,或是在使用函数时提供二元比较函数。通常推荐定义 `operator<`。[^note1]
+
+示例:
 
 ```cpp
 int a[1009], n = 10;
@@ -88,9 +106,11 @@ struct data {
     return (a == rhs.a) ? (b < rhs.b) : (a < rhs.a);
   }
 } da[1009];
+
 bool cmp(const data u1, const data u2) {
   return (u1.a == u2.a) ? (u1.b > u2.b) : (u1.a > u2.a);
 }
+
 // ......
 std::sort(da + 1, da + 1 + 10);  // 使用结构体中定义的 < 运算符,从小到大排序。
 std::sort(da + 1, da + 1 + 10, cmp);  // 使用 cmp 函数进行比较,从大到小排序。
@@ -98,11 +118,13 @@ std::sort(da + 1, da + 1 + 10, cmp);  // 使用 cmp 函数进行比较,从大
 
 ### 严格弱序
 
-进行排序的运算符必须满足严格弱序( [Strict weak orderings](https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings) ),否则会出现不可预料的情况(如运行时错误、无法正确排序)。
+参见:[Strict weak orderings](https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings)
+
+进行排序的运算符必须满足严格弱序,否则会出现不可预料的情况(如运行时错误、无法正确排序)。
 
 严格弱序的要求:
 
-1.  $x \not< x$ (非自反性)
+1. $x \not< x$ (非自反性)
 2. 若 $x < y$ ,则 $y \not< x$ (非对称性)
 3. 若 $x < y, y < z$ ,则 $x < z$ (传递性)
 4. 若 $x \not< y, y \not< x, y \not< z, z \not< y$ ,则 $x \not< z, z \not< x$ (不可比性的传递性)
@@ -110,5 +132,13 @@ std::sort(da + 1, da + 1 + 10, cmp);  // 使用 cmp 函数进行比较,从大
 常见的错误做法:
 
 - 使用 `<=` 来定义排序中的小于运算符。
-- 在调用排序运算符时,读取外部数值可能会改变的数组。(常见于最短路算法)
-- 将多个数的最大最小值进行比较的结果作为排序运算符。(如,皇后游戏/加工生产调度 中的经典错误,可以参考文章 [浅谈邻项交换排序的应用以及需要注意的问题](https://ouuan.github.io/浅谈邻项交换排序的应用以及需要注意的问题/) )。
+- 在调用排序运算符时,读取外部数值可能会改变的数组(常见于最短路算法)。
+- 将多个数的最大最小值进行比较的结果作为排序运算符(如皇后游戏/加工生产调度 中的经典错误)。
+
+## 外部链接
+
+- [浅谈邻项交换排序的应用以及需要注意的问题](https://ouuan.github.io/浅谈邻项交换排序的应用以及需要注意的问题/)
+
+## 参考资料与注释
+
+[^note1]: 因为大部分标准算法默认使用 `operator<` 进行比较。