# 排序(线性对数时间复杂度排序算法)
开篇问题:如何在 $O(n)$ 时间复杂度内寻找一个无序数组中第 K 大的元素?
## 归并排序
* 归并排序使用了「分治」思想(Divide and Conquer)
* 分:把数组分成前后两部分,分别排序
* 合:将有序的两部分合并
![归并排序分解图](https://static001.geekbang.org/resource/image/db/2b/db7f892d3355ef74da9cd64aa926dc2b.jpg)
* 分治与递归
* 分治:解决问题的处理办法
* 递归:实现算法的手段
* ——分治算法经常用递归来实现
* 递归实现:
* 终止条件:区间 `[first, last)` 内不足 2 个元素
* 递归公式:`merge_sort(first, last) = merge(merge_sort(first, mid), merge_sort(mid, last))`,其中 `mid = first + (last - first) / 2`
C++ 实现:
```cpp
template <typename FrwdIt,
typename T = typename std::iterator_traits<FrwdIt>::value_type,
typename BinaryPred = std::less<T>>
void merge_sort(FrwdIt first, FrwdIt last, BinaryPred comp = BinaryPred()) {
const auto len = std::distance(first, last);
if (len <= 1) { return; }
auto cut = first + len / 2;
merge_sort(first, cut, comp);
merge_sort(cut, last, comp);
std::vector<T> tmp;
tmp.reserve(len);
detail::merge(first, cut, cut, last, std::back_inserter(tmp), comp);
std::copy(tmp.begin(), tmp.end(), first);
}
```
这里涉及到一个 `merge` 的过程,它的实现大致是:
```cpp
namespace detail {
template <typename InputIt1, typename InputIt2, typename OutputIt,
typename BinaryPred = std::less<typename std::iterator_traits<InputIt1>::value_type>>
OutputIt merge(InputIt1 first1, InputIt1 last1,
InputIt2 first2, InputIt2 last2,
OutputIt d_first,
BinaryPred comp = BinaryPred()) {
for (; first1 != last1; ++d_first) {
if (first2 == last2) {
return std::copy(first1, last1, d_first);
}
if (comp(*first2, *first1)) {
*d_first = *first2;
++first2;
} else {
*d_first = *first1;
++first1;
}
}
return std::copy(first2, last2, d_first);
}
} // namespace detail
```
![`merge` 的过程](https://static001.geekbang.org/resource/image/95/2f/95897ade4f7ad5d10af057b1d144a22f.jpg)
### 算法分析
* 稳定性
* 由于 `comp` 是严格偏序,所以 `!comp(*first2, *first1)` 时,取用 `first1` 的元素放入 `d_first` 保证了算法稳定性
* 时间复杂度
* 定义 $T(n)$ 表示问题规模为 $n$ 时算法的耗时,
* 有递推公式:$T(n) = 2T(n/2) + n$
* 展开得 $T(n) = 2^{k}T(1) + k * n$
* 考虑 $k$ 是递归深度,它的值是 $\log_2 n$,因此 $T(n) = n + n\log_2 n$
* 因此,归并排序的时间复杂度为 $\Theta(n\log n)$
* 空间复杂度
* 一般来说,空间复杂度是 $\Theta(n)$
## 快速排序(quick sort,快排)
原理:
* 在待排序区间 `[first, last)` 中选取一个元素,称为主元(pivot,枢轴)
* 对待排序区间进行划分,使得 `[first, cut)` 中的元素满足 `comp(element, pivot)` 而 `[cut, last)` 中的元素不满足 `comp(element, pivot)`
* 对划分的两个区间,继续划分,直到区间 `[first, last)` 内不足 2 个元素
![快排分区示例](https://static001.geekbang.org/resource/image/4d/81/4d892c3a2e08a17f16097d07ea088a81.jpg)
显然,这又是一个递归:
* 终止条件:区间 `[first, last)` 内不足 2 个元素
* 递归公式:`quick_sort(first, last) = quick_sort(first, cut) + quick_sort(cut, last)`
```cpp
template <typename IterT, typename T = typename std::iterator_traits<IterT>::value_type>
void quick_sort(IterT first, IterT last) {
if (std::distance(first, last) > 1) {
IterT prev_last = std::prev(last);
IterT cut = std::partition(first, prev_last, [prev_last](T v) { return v < *prev_last; });
std::iter_swap(cut, prev_last);
quick_sort(first, cut);
quick_sort(cut, last);
}
}
```
> 一点优化(Liam Huang):通过将 `if` 改为 `while` 同时修改 `last` 迭代器的值,可以节省一半递归调用的开销。
```cpp
template <typename IterT, typename T = typename std::iterator_traits<IterT>::value_type>
void quick_sort(IterT first, IterT last) {
while (std::distance(first, last) > 1) {
IterT prev_last = std::prev(last);
IterT cut = std::partition(first, prev_last, [prev_last](T v) { return v < *prev_last; });
std::iter_swap(cut, prev_last);
quick_sort(cut, last);
last = cut;
}
}
```
如果不要求空间复杂度,分区函数实现起来很容易。
![非原地分区](https://static001.geekbang.org/resource/image/66/dc/6643bc3cef766f5b3e4526c332c60adc.jpg)
若要求原地分区,则不那么容易了。下面的实现实现了原地分区函数,并且能将所有相等的主元排在一起。
```cpp
template <typename BidirIt,
typename T = typename std::iterator_traits<BidirIt>::value_type,
typename Compare = std::less<T>>
std::pair<BidirIt, BidirIt> inplace_partition(BidirIt first,
BidirIt last,
const T& pivot,
Compare comp = Compare()) {
BidirIt last_less, last_greater, first_equal, last_equal;
for (last_less = first, last_greater = first, first_equal = last;
last_greater != first_equal; ) {
if (comp(*last_greater, pivot)) {
std::iter_swap(last_greater++, last_less++);
} else if (comp(pivot, *last_greater)) {
++last_greater;
} else { // pivot == *last_greater
std::iter_swap(last_greater, --first_equal);
}
}
const auto cnt = std::distance(first_equal, last);
std::swap_ranges(first_equal, last, last_less);
first_equal = last_less;
last_equal = first_equal + cnt;
return {first_equal, last_equal};
}
```
### 算法分析
* 稳定性
* 由于 `inplace_partition` 使用了大量 `std::iter_swap` 操作,所以不是稳定排序
* 时间复杂度
* 定义 $T(n)$ 表示问题规模为 $n$ 时算法的耗时,
* 有递推公式:$T(n) = 2T(n/2) + n$(假定每次分割都是均衡分割)
* 展开得 $T(n) = 2^{k}T(1) + k * n$
* 考虑 $k$ 是递归深度,它的值是 $\log_2 n$,因此 $T(n) = n + n\log_2 n$
* 因此,快速排序的时间复杂度为 $\Theta(n\log n)$
* 空间复杂度
* 一般来说,空间复杂度是 $\Theta(1)$,因此是原地排序算法
## 开篇问题
* 分区,看前半段元素数量
* 前半段元素数量 < K,对后半段进行分区
* 前半段元素数量 > K,对前半段进行分区
* 前半段元素数量 = K,前半段末位元素即是所求
没有合适的资源?快使用搜索试试~ 我知道了~
数据结构和算法必知必会的50个代码实现.zip
共626个文件
go:70个
scala:69个
py:63个
需积分: 3 0 下载量 13 浏览量
2024-09-05
21:32:42
上传
评论
收藏 1.04MB ZIP 举报
温馨提示
数据结构和算法必知必会的50个代码实现.zip 数组 实现一个支持动态扩容的数组 实现一个大小固定的有序数组,支持动态增删改操作 实现两个有序数组合并为一个有序数组 链表 实现单链表、循环链表、双向链表,支持增删操作 实现单链表反转 实现两个有序的链表合并为一个有序链表 实现求链表的中间结点 栈 用数组实现一个顺序栈 用链表实现一个链式栈 编程模拟实现一个浏览器的前进、后退功能 队列 用数组实现一个顺序队列 用链表实现一个链式队列 实现一个循环队列 递归 编程实现斐波那契数列求值f(n)=f(n-1)+f(n-2) 编程实现求阶乘n! 编程实现一组数据集合的全排列 排序 实现归并排序、快速排序、插入排序、冒泡排序、选择排序 编程实现O(n)时间复杂度内找到一组数据的第K大元素 二分查找 实现一个有序数组的二分查找算法 实现模糊二分查找算法(比如大于等于给定值的第一个元素) 散列表 实现一个基于链表法解决冲突问题的散列表 实现一个LRU缓存淘汰算法 字符串 实现一个字符集,只包含a~z这26个英文字母的Trie树 实现朴素的字符串匹配算法 二叉树 实现一个二叉查找树,并且支持插入、...
资源推荐
资源详情
资源评论
收起资源包目录
数据结构和算法必知必会的50个代码实现.zip (626个子文件)
32_BFRK 927B
LinkedHashMap.c 7KB
listhash.c 6KB
LinkedListAlgo.c 6KB
skiplist.c 6KB
binarysearchtree.c 6KB
bsearch_variant.c 5KB
sort.c 5KB
Array_gp.c 5KB
singleList.c 4KB
Dlist.c 4KB
binarysearchtree.c 4KB
skiplist.c 4KB
binarytree.c 4KB
bsearch.c 4KB
hashtable.c 3KB
single_list.c 3KB
array_queue.c 3KB
bst.c 3KB
bst.c 3KB
arrayStack.c 3KB
list_queue.c 3KB
heap.c 2KB
ring_queue.c 2KB
graph.c 2KB
merge_sort.c 2KB
sorts.c 2KB
quick_sort.c 2KB
linklist_stack.c 2KB
sorts_jinshaohui.c 2KB
linklist_jinshaohui.c 2KB
binary_search.c 2KB
Trie.c 2KB
bsearch.c 2KB
array.c 2KB
list_queue.c 1KB
binarytree.c 1KB
merge_sort.c 1KB
counting_sort.c 1KB
one_two_step.c 1004B
quick_sort.c 906B
sqrt.c 881B
skiplist_tr_test.cc 3KB
hash_map.cc 3KB
dynamic_array_queue_test.cc 2KB
circular_queue_test.cc 2KB
one_two_step.cc 2KB
array_queue_test.cc 2KB
linked_queue_test.cc 2KB
bsearch_varients_test.cc 2KB
skiplist_test.cc 1KB
counting_sort_test.cc 1KB
sorts_test.cc 1KB
bucket_sort_test.cc 1015B
bsearch_test.cc 834B
quick_sort_test.cc 599B
merge_sort_test.cc 543B
SingleList.cpp 13KB
SkipList.cpp 8KB
binary_search_tree.cpp 8KB
LRUBasedLinkedList.cpp 5KB
StackBasedOnLinkedList.cpp 4KB
StackBasedOnArray.cpp 3KB
palindromeList.cpp 2KB
LinkList.cpp 2KB
sorts.cpp 922B
main.cpp 753B
Array.Tests.cs 6KB
SingleLinkedListAlgo.cs 5KB
SingleLinkedList.Tests.cs 5KB
SingleLinkedListAlgo.Tests.cs 4KB
SingleLinkedList.cs 3KB
Array.cs 3KB
LinkedStackBrowser.Tests.cs 3KB
ArrayStack.Tests.cs 2KB
LRUWithArray.Tests.cs 2KB
LinkedStack.Tests.cs 2KB
LRUWithLinkedList.cs 1KB
LRUWithLinkedList.Tests.cs 1KB
LRUWithArray.cs 1KB
LinkedStack.cs 959B
LinkedStackBrowser.cs 906B
ArrayStack.cs 817B
BaseLinkedListTests.cs 450B
algo07_linkedlist_tests.csproj 749B
algo06_linkedlist_tests.csproj 596B
algo05_array_tests.csproj 564B
algo08_stack_tests.csproj 562B
algo06_linked_list.csproj 273B
algo07_linkedlist.csproj 265B
algo05_array.csproj 175B
algo08_stack.csproj 148B
.DS_Store 6KB
f21 1KB
.gitignore 536B
.gitignore 69B
.gitignore 23B
.gitignore 8B
.gitkeep 0B
.gitkeep 0B
共 626 条
- 1
- 2
- 3
- 4
- 5
- 6
- 7
资源评论
武昌库里写JAVA
- 粉丝: 6524
- 资源: 3159
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功