1.1.1 排序算法库函数
C++
STL:algorithm库的sort()
函数 (可配合lambda表达式使用)
template< class RandomIt, class Compare >
void sort( RandomIt first, RandomIt last, Compare comp );
Java
-
数组:
Arrays.sort(array, first, last)
-
容器:
- Collections 类提供的
Collection.sort(collection)
方法 - 自身的
collection.sort(Comparator c)
方法
- Collections 类提供的
Python
-
列表:
list.sort()
方法 -
内置函数:
sorted(iterable, /, *, key=None, reverse=False)
key 形参的值应该是个函数(或其他可调用对象),它接受一个参数并返回一个用于排序的键。
1.1.2 快速排序
原理
思路:
- 选定分界点;
- 调整序列中的元素,使当前序列最左端的元素在调整后满足左侧所有元素均小于等于分界点,右侧所有元素均大于分界点;
- 对左右两侧递归调整,直到当前调整区间的长度不超过1。
实现:双指针思想
- 令 i i i , j j j 双指针分别指向序列首尾
- 只要 i i i 指向的元素小于等于分界点,就将 i i i 不断右移,直到 i i i 指向的元素大于分界点;
- 只要 j j j 指向的元素大于分界点,就将 j j j 不断左移,直到 j j j 指向的元素小于等于分界点;
- 交换 i i i , j j j 双指针指向的元素,这样实现了一对元素的调整
- 重复第二步和第三步,直到 i i i , j j j 双指针相遇
模板
C++
注意点:因为是do while
循环,先做了++ i
和-- j
,所以int i = l - 1, j = r + 1;
而且边界条件是i < j
而不是i <= j
。
const int N = 1e6 + 10;
int nums[N], n;
void quickSort(int l, int r)
{
if (l >= r) return; // 如果当前区间已经没有元素则返回
int i = l - 1, j = r + 1, x = nums[l + r >> 1]; // 设置双指针i,j和分界点x
while (i < j) { // 当i小于j时
do ++ i ; while (nums[i] < x); //只要i指向的元素小于等于分界点,就将i不断右移,直到i指向的元素大于分界点
do -- j ; while (nums[j] > x); //只要j指向的元素大于分界点,就将j不断左移,直到j指向的元素小于等于分界点
if (i < j) swap(nums[i], nums[j]); //交换i,j双指针指向的元素
}
quickSort(l, j), quickSort(j + 1, r); // 递归排序左右区间
}
简易随机快排:
快速排序算法在序列中元素的排列比较随机时效率最高,但是当序列中的元素接近有序时,会达到最坏的时间复杂度
O
(
n
2
)
O(n^2)
O(n2),随机快排有两种实现方法,为把序列随机打乱和随机选择主元。
随机化序列:algorithm头文件下的[std::shuffle(first, end, g);]
#include <random>
#include <algorithm>
std::random_device rd;
std::mt19937 g(rd());
// 把数组或容器左闭右开区间[0, n)中的元素随机打乱
std::shuffle(q, q + n, g);
quickSort(q, 0, n - 1) // 快排板子
Java
public class Main{
public static void quickSort(int[] q, int l, int r) {
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l+r>>1];
while (i < j) {
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) {
int temp = q[i]; q[i] = q[j]; q[j] = temp;
}
}
quickSort(q, l, j);
quickSort(q, j + 1, r);
}
}
Python
Python 3.8之前:
def quickSort(nums, l, r):
if l >= r:
return
i, j, x = l - 1, r + 1, nums[l + r >> 1]
while i < j:
while True:
i += 1
if nums[i] >= x:
break
while True:
j -= 1
if nums[j] <= x:
break
if i < j: nums[i], nums[j] = nums[j], nums[i]
quickSort(nums, l, j)
quickSort(nums, j + 1, r)
Python 3.8之后(支持海象运算符 :=):
def quickSort(nums, l, r):
if l >= r:
return
i, j, x = l - 1, r + 1, nums[l + r >> 1]
while i < j:
while (i := i + 1) >= 0:
if nums[i] >= x:
break
while (j := j - 1) >= 0:
if nums[j] <= x:
break
if i < j: nums[i], nums[j] = nums[j], nums[i]
quickSort(nums, l, j)
quickSort(nums, j + 1, r)
1.1.3 快速选择
原理
快速选择算法解决这样一个问题:从一个无序的数组中求出第
k
k
k 大的数。
对区间
[
l
,
r
]
[l, r]
[l,r] 进行一次快速排序后,分界点
n
u
m
s
[
x
]
nums[x]
nums[x] 左侧的元素都小于等于分界点,分界点
n
u
m
s
[
x
]
nums[x]
nums[x] 右侧的元素都大于分界点,则分界点
n
u
m
s
[
x
]
nums[x]
nums[x] 就是区间
[
l
,
r
]
[l, r]
[l,r] 中第
x
−
l
+
1
x-l+1
x−l+1 大的数,因此:
- 当 k = x − l + 1 k=x-l+1 k=x−l+1 时,第 k k k 大的数就是分界点 n u m s [ x ] nums[x] nums[x] ;
- 当 k < x − l + 1 k<x-l+1 k<x−l+1 时,第 k k k 大的数在分界点 n u m s [ x ] nums[x] nums[x] 的左侧,向左侧区间递归;
- 当 k > x − l + 1 k>x-l+1 k>x−l+1 时,第 k k k 大的数在分界点 n u m s [ x ] nums[x] nums[x] 的右侧,向右侧区间递归;
- 因为递归边界是 l > = r l>=r l>=r ,则返回 n u m s [ l ] nums[l] nums[l]
随机选择只需要把随机快排的步骤迁移过来即可。
模板
C++
const int N = 100010;
int n, k, nums[N];
int quickSelect(int l, int r, int k)
{
if (l >= r) return nums[l];
int i = l - 1, j = r + 1, x = nums[l + r >> 1];
while (i < j) {
do ++ i; while (nums[i] < x);
do -- j; while (nums[j] > x);
if (i < j) swap(nums[i], nums[j]);
} // 一次快排完成后,分界点x就是区间里第j-l+1大的元素
if (k <= j - l + 1) return quickSelect(l, j, k); // 如果k<=j-l+1,第k大的数在x左侧,向左递归
else return quickSelect(j + 1, r, k - (j - l + 1)); // 如果k>j-l+1,第k大的数在x右侧,向右递归
}
1.1.4 归并排序
原理
有序序列的归并:利用有序序列的单调性质,设置双指针对序列进行扫描,合并后的序列保持单调性不变。
- 令 i i i , j j j 双指针分别指向两个递增序列 A A A , B B B 的首元素,设置新的空序列 C C C ;
- 若 A [ i ] A[i] A[i] 小于等于 B [ j ] B[j] B[j] ,则 A [ i ] A[i] A[i] 是当前序列 A A A 和 B B B 的剩余元素中最小的那个,把 A [ i ] A[i] A[i] 加入序列 C C C 中,并让 i i i 加 1;
- 若 A [ i ] A[i] A[i] 大于 B [ j ] B[j] B[j] ,则 B [ j ] B[j] B[j] 是当前序列 A A A 和 B B B 的剩余元素中最小的那个,把 B [ j ] B[j] B[j] 加入序列 C C C 中,并让 j j j 加 1;
- 重复第2,3步操作,直到双指针 i i i , j j j 有任意一个达到序列末端为止,然后将另一个序列剩下的所有元素依次归入序列 C C C 中。
归并排序:反复归并序列使其最终达到有序。归并排序需要设置一个和原数组大小一样的辅助数组。
- 把序列两两分组,将序列归并为 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈2n⌉ 个组,组内单独排序;
- 将 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil ⌈2n⌉ 个组两两归并为 ⌈ n 4 ⌉ \lceil \frac{n}{4} \rceil ⌈4n⌉ 个组,组内单独排序;
- 重复两两归并-单独排序过程,直到只剩下一个组为止
可参考:【归并排序动画演示】
模板
C++
const int N = 100010;
int nums[N], tmps[N], n;
void mergeSort(int l, int r) {
if (l >= r) return; // 如果当前区间已经没有元素则返回
int mid = l + r >> 1, k = 0;
mergeSort(l, mid), mergeSort(mid + 1, r); // 递归排序左右两侧
int i = l, j = mid + 1; // 设置双指针
while (i <= mid && j <= r) // 将左右区间有序归并
if (nums[i] <= nums[j]) tmps[k ++] = nums[i ++];
else tmps[k ++] = nums[j ++];
while (i <= mid) tmps[k ++] = nums[i ++]; // 把左区间剩下部分归入新序列
while (j <= r) tmps[k ++] = nums[j ++]; // 把右区间剩下部分归入新序列
for (i = l, j = 0; i <= r; ++ i, ++ j) nums[i] = tmps[j]; // 把辅助数组的值归位到原数组
}