Timsort
Timsort 在 Python, Java 等编程语言的标准库中都有使用, 综合性能比较好.
Timsort 是对归并排序(merge sort)的优化.
Timsort 的步骤
它的优化思路是:
- 先将数组分成相同间隔的子数组, 常用的间隔值是 32 或者 24
- 然后用插入排序(或者考虑用希尔排序) 对这些子数组进行排序, 因为这些子数组比较短小, 插入排序的效率比较高
- 排序后, 依次将子数组合并在一起形成有序的大数组, 直到整个数组变得有序
- 合并子数组的方法与归并排序里一致, 不再详述
- 如果数组中的元素较少, 就只会使用插入排序
下图展示了 timsort 的一个示例:
Timsort 的实现
#![allow(unused)] fn main() { use crate::insertion_sort::insertion_sort; use crate::shell_sort::shell_sort; /// Timsort 是对归并排序 (merge sort) 的优化. pub fn timsort<T>(arr: &mut [T]) where T: PartialOrd + Clone, { const RUN: usize = 32; let len = arr.len(); if len < 2 { return; } // 先将数组分隔成大小相同的子数组, 并利用插入排序进行排序. // 插入排序比较善于处理已基本有序的较小的数组. for i in (0..len).step_by(RUN) { let end = (i + RUN).min(len); insertion_sort(&mut arr[i..end]); } // 然后将各个子数组合并在一起 // 数组间隔依次是 RUN, RUN * 2, RUN * 4, ... let mut size = RUN; while size < len { // 合并子数组 for left in (0..len).step_by(2 * size) { // 两个子数组分别是 `arr[left..=middle]` 和 `arr[middle+1..=right]`. let middle = left + size - 1; let right = (left + 2 * size - 1).min(len - 1); if middle < right { merge(arr, left, middle, right); } } size *= 2; } } /// 合并子数组 `arr[left..=middle]` 和 `arr[middle+1..=right]` fn merge<T>(arr: &mut [T], left: usize, middle: usize, right: usize) where T: PartialOrd + Clone, { // 先创建辅助数组 let aux_left = arr[left..=middle].to_vec(); let aux_right = arr[middle + 1..=right].to_vec(); let left_len = middle - left + 1; let right_len = right - middle; // 合并子数组 let mut i = 0; let mut j = 0; let mut k = left; while i < left_len && j < right_len { if aux_left[i] < aux_right[j] { arr[k].clone_from(&aux_left[i]); i += 1; } else { arr[k].clone_from(&aux_right[j]); j += 1; } k += 1; } // 最后复制剩下的元素 while i < left_len { arr[k].clone_from(&aux_left[i]); i += 1; k += 1; } while j < right_len { arr[k].clone_from(&aux_right[j]); j += 1; k += 1; } } /// 其思路是, 先将前 i 个元素调整为增序的, 随着 i 从 0 增大到 n, 整个序列就变得是增序了. pub fn insertion_sort<T>(arr: &mut [T]) where T: PartialOrd, { let len = arr.len(); for i in 1..len { for j in (1..=i).rev() { if arr[j - 1] > arr[j] { arr.swap(j - 1, j); } else { break; } } } } }
使用希尔排序代替插入排序
上面提到了, 可以用希尔排序来代替插入排序, 可以将子数组的间隔设置得更大些, 我们选取 RUN = 64;
#![allow(unused)] fn main() { /// 使用希尔排序代替插入排序 /// /// 只创建一次辅助数组 pub fn shell_timsort<T>(arr: &mut [T]) where T: PartialOrd + Clone, { const RUN: usize = 64; let len = arr.len(); if len < 2 { return; } // 先将数组分隔成大小相同的子数组, 并利用插入排序进行排序. // 插入排序比较善于处理已基本有序的较小的数组. for i in (0..len).step_by(RUN) { let end = (i + RUN).min(len); shell_sort(&mut arr[i..end]); } // 然后将各个子数组合并在一起 // 数组间隔依次是 RUN, RUN * 2, RUN * 4, ... let mut size = RUN; let mut aux = arr.to_vec(); while size < len { // 合并子数组 for left in (0..len).step_by(2 * size) { // 两个子数组分别是 `arr[left..=middle]` 和 `arr[middle+1..=right]`. let middle = left + size - 1; let right = (left + 2 * size - 1).min(len - 1); if middle < right { merge_with_aux(arr, left, middle, right, &mut aux); } } size *= 2; } } /// 合并子数组 `arr[left..=middle]` 和 `arr[middle+1..=right]` fn merge_with_aux<T>(arr: &mut [T], left: usize, middle: usize, right: usize, aux: &mut [T]) where T: PartialOrd + Clone, { // 先初始化辅助数组 for i in left..=right { aux[i].clone_from(&arr[i]); } // 合并子数组 let mut i = left; let mut j = middle + 1; let mut k = left; while i <= middle && j <= right { if aux[i] < aux[j] { arr[k].clone_from(&aux[i]); i += 1; } else { arr[k].clone_from(&aux[j]); j += 1; } k += 1; } while i <= middle { arr[k].clone_from(&aux[i]); i += 1; k += 1; } while j <= right { arr[k].clone_from(&aux[j]); j += 1; k += 1; } } #[cfg(test)] mod tests { use crate::timsort::{shell_timsort, timsort}; #[test] fn test_timsort() { /// Shell sort is a simple extension to insertion sort that allows exchanging /// elements that far apart. /// /// It produces partially sorted array (h-sorted array). pub fn shell_sort<T>(arr: &mut [T]) where T: PartialOrd, { const FACTOR: usize = 3; let len = arr.len(); // 计算第一个 gap 的值, 大概是 len/3 let mut h = 1; while h < len / FACTOR { h = FACTOR * h + 1; } while h >= 1 { // 使用插入排序, 将 `arr[0..h]` 排序好 for i in h..len { let mut j = i; while j >= h && arr[j - h] > arr[j] { arr.swap(j - h, j); j -= h; } } h /= FACTOR; } } }
Timsort 的特点
- 最差情况下的时间复杂度是:
O(n log(n))
, 最间复杂度是O(n)
- 如果数组已基本有序, 最好情况下的时间复杂度是
O(n)
- 是稳定排序, 不是原地排序 (in-place sort)
- 与归并排序不同的是, 它不需要递归调用自身将数组分成左右子数组
- timsort 与插入归并排序的区别较大