STL的sort()算法
STL的sort()算法
灵魂追问
- STL里sort算法用的是什么排序算法?
- 数据量大和数据量小都适合用快速排序吗?
- 快速排序的时间复杂度不是稳定的nlogn,最坏情况会变成n^2,怎么解决复杂度恶化问题?
- 快速排序递归实现时,怎么解决递归层次过深的问题?
- 递归过深会引发什么问题?
- 怎么控制递归深度?如果达到递归深度了还没排完序怎么办?
sort源码
以下代码截自vs2019的的algorithm
sort函数原型
可以看到排序是对左闭右开的区间进行,而且默认情况下less<>()
指定内置类型从小到大。
template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last), using _Pred
_Adl_verify_range(_First, _Last);
const auto _UFirst = _Get_unwrapped(_First);
const auto _ULast = _Get_unwrapped(_Last);
_Sort_unchecked(_UFirst, _ULast, _ULast - _UFirst, _Pass_fn(_Pred));
}
template <class _RanIt>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last) { // order [_First, _Last), using operator<
_STD sort(_First, _Last, less<>());
}
sort函数实现
template <class _RanIt, class _Pr>
_CONSTEXPR20 void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred) {
// order [_First, _Last), using _Pred
for (;;) {
if (_Last - _First <= _ISORT_MAX) { // small
_Insertion_sort_unchecked(_First, _Last, _Pred);
return;
}
if (_Ideal <= 0) { // heap sort if too many divisions
_Make_heap_unchecked(_First, _Last, _Pred);
_Sort_heap_unchecked(_First, _Last, _Pred);
return;
}
// divide and conquer by quicksort
auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);
_Ideal = (_Ideal >> 1) + (_Ideal >> 2); // allow 1.5 log2(N) divisions
if (_Mid.first - _First < _Last - _Mid.second) { // loop on second half
_Sort_unchecked(_First, _Mid.first, _Ideal, _Pred);
_First = _Mid.second;
} else { // loop on first half
_Sort_unchecked(_Mid.second, _Last, _Ideal, _Pred);
_Last = _Mid.first;
}
}
}
分析:
- 当区间长度小于一定值时,改用插入排序。vs2019下这个值是32
_INLINE_VAR constexpr int _ISORT_MAX = 32; // maximum size for insertion sort
再来看看插入排序的实现,使用了移动语义来替代拷贝
// FUNCTION TEMPLATE sort
template <class _BidIt, class _Pr>
_CONSTEXPR20 _BidIt _Insertion_sort_unchecked(_BidIt _First, const _BidIt _Last, _Pr _Pred) {
// insertion sort [_First, _Last), using _Pred
if (_First != _Last) {
for (_BidIt _Next = _First; ++_Next != _Last;) { // order next element
_BidIt _Next1 = _Next;
_Iter_value_t<_BidIt> _Val = _STD move(*_Next);
if (_DEBUG_LT_PRED(_Pred, _Val, *_First)) { // found new earliest element, move to front
_Move_backward_unchecked(_First, _Next, ++_Next1);
*_First = _STD move(_Val);
} else { // look for insertion point after first
for (_BidIt _First1 = _Next1; _DEBUG_LT_PRED(_Pred, _Val, *--_First1); _Next1 = _First1) {
*_Next1 = _STD move(*_First1); // move hole down
}
*_Next1 = _STD move(_Val); // insert element in hole
}
}
}
return _Last;
}
至于为什么选择插入排序,因为经过之前的快排,数据已经相对有序。
- 使用
_Ideal
来控制递归深度,当快排的初始序列是逆序时,复杂度是O(N2)。变量_Ideal
初始化为区间长度,每一次划分会执行
_Ideal = (_Ideal >> 1) + (_Ideal >> 2); // allow 1.5 log2(N) divisions
当_Ideal <= 0
会进行堆排序,至于为什么选择堆排序,因为复杂度稳定为O(Nlog2N)。
注:
不是所有STL容器都适合sort()。首先,关系型容器底层是红黑树,自动排序所以不需要。其次,栈和优先队列等限制出入口的容器不允许排序。
保持学习,保持思考,保持对世界的好奇心!