一.原理分析:
首先,我们要知道STL中的sort用到了快速排序,堆排序和插入排序.这几种基础排序在这里就不讲了.
但是我们要知道这几种排序的优缺点:
快速排序:
时间复杂度:平均O(N*logN),最坏O(N^2); 优点:大部分情况性能很好.缺点:时间复杂度不稳定,数据量大时递归深度大.
堆排序:
时间复杂度:O(N*logN); 优点:时间复杂度稳定,适合数据量大的数据. 缺点:一:堆排序对数据的访问是跳跃的,对缓存不友好.二:建堆时会降低数据有序度,使堆排序的交换次数比快速排序多,时间常数也更大.
插入排序:
时间复杂度:O(N^2); 优点:数据量小或数据基本有序时效率高.缺点:时间复杂度高.
std::sort就是根据这几种排序的优缺点进行了结合.
- 快速排序到 2*logN 的深度限制后采用堆排序。
- 快速排序+堆排序后,数据已经被分为多个数据量小于等于阈值(16)的子区域,子区域里面的数据可能是无序的,但是子区域之间已经是有序了。
- 最后整个基本有序数据区域采用插入排序,达到完全有序。
其中快排+堆排的结合就是introsort(内省排序)
二.std::sort 源码:
template<typename _RandomAccessIterator, typename _Compare>
inline void
__sort(_RandomAccessIterator __first, _RandomAccessIterator __last,
_Compare __comp)//comp默认为less()
{
if (__first != __last)
{
/* __introsort_loop先进行一遍IntroSort,但是不是严格意义上的IntroSort。
*因为执行完之后区间并不是完全有序的,而是基本有序的。
*__introsort_loop和IntroSort不同的地方是,__introsort_loop会在一开始会判断区间的大小,
*当区间小于16的时候,就直接返回。这样就得到了很多个区间有序的小分块.
*第三个参数是获得2*logN,为递归深度限制,当达到此限制时改用堆排序.*/
std::__introsort_loop(__first, __last,
std::__lg(__last - __first) * 2,
__comp);
// 在区间基本有序的基础上再做一遍插入排序,使区间完全有序
std::__final_insertion_sort(__first, __last, __comp);
}
}
2.1 __introsort_loop代码:
enum { _S_threshold = 16 };//分块阈值
template<typename _RandomAccessIterator, typename _Size, typename _Compare>
void
__introsort_loop(_RandomAccessIterator __first,
_RandomAccessIterator __last,
_Size __depth_limit, _Compare __comp)
{
// 若区间大小<=16就不再排序。
while (__last - __first > int(_S_threshold))
{
// 若递归深度达到限制,就改用堆排序
if (__depth_limit == 0)
{
std::__partial_sort(__first, __last, __last, __comp);
return;
}
--depth_limit;
/* 递归排序,并返回排序区域被分成两个区域后,区域的分界位置 __cut。 */
_RandomAccessIterator __cut =
std::__unguarded_partition_pivot(__first, __last, __comp);
std::__introsort_loop(__cut, __last, __depth_limit, __comp);//对右半区域递归调用
__last = __cut; // 单边递归,这么做能减少次数
}
}
2.1.1 __introsort_loop中用到的函数代码
template<typename _RandomAccessIterator, typename _Compare>
inline _RandomAccessIterator
__unguarded_partition_pivot(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp) {
_RandomAccessIterator __mid = __first + (__last - __first) / 2;
/* 比较三个值:第一个位置 ,最后一个位置,中间位置这三个值。
* 哪个位置上的数据处于中间的(a < b < c 取 b)作为快速排序的哨兵。
* 然后将哨兵数据与第一个位置数据交换。 */
std::__move_median_to_first(__first, __first + 1, __mid, __last - 1, __comp);
/* 第一个位置上的数据是哨兵,那么将 [__first + 1, __last) 这个区间的数据
* 根据哨兵的值分成左右两部分.*/
return std::__unguarded_partition(__first + 1, __last, __first, __comp);
}
/* 三个数取中值,放在 __result 位置。*/
template<typename _Iterator, typename _Compare>
void __move_median_to_first(_Iterator __result,_Iterator __a, _Iterator __b,
_Iterator __c, _Compare __comp) {
if (__comp(__a, __b)) {
if (__comp(__b, __c))
std::iter_swap(__result, __b);
else if (__comp(__a, __c))
std::iter_swap(__result, __c);
else
std::iter_swap(__result, __a);
}
else if (__comp(__a, __c))
std::iter_swap(__result, __a);
else if (__comp(__b, __c))
std::iter_swap(__result, __c);
else
std::iter_swap(__result, __b);
}
/* 根据哨兵,将数据区域分成两部分,并返回区域分界位置。 */
template<typename _RandomAccessIterator, typename _Compare>
_RandomAccessIterator
__unguarded_partition(_RandomAccessIterator __first,
_RandomAccessIterator __last,
_RandomAccessIterator __pivot, _Compare __comp) {
while (true) {
while (__comp(__first, __pivot))
++__first;
--__last;
while (__comp(__pivot, __last))
--__last;
if (!(__first < __last))
return __first;
std::iter_swap(__first, __last);
++__first;
}
}
2.2 __final_insertion_sort代码:
template<typename _RandomAccessIterator, typename _Compare>
void
__final_insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp)
{
if (__last - __first > int(_S_threshold)) // 区间长度大于16
{
// 插入排序
std::__insertion_sort(__first, __first + int(_S_threshold), __comp);
// 也是插入排序,只是在插入排序的内循环时,不再判断边界条件,因为已经保证了区间前面肯定有比待插入元素更小的元素
std::__unguarded_insertion_sort(__first + int(_S_threshold), __last,
__comp);
}
else // 区间长度小于等于16的话
std::__insertion_sort(__first, __last, __comp); // 插入排序
}
2.2.1 __final_insertion_sort用到的函数代码:
template <typename _RandomAccessIterator, typename _Compare>
void
__insertion_sort(_RandomAccessIterator __first,
_RandomAccessIterator __last, _Compare __comp) {
if (__first == __last) return;
for (_RandomAccessIterator __i = __first + 1; __i != __last; ++__i) {
/* 如果当前数据比第一个数据还要小,[__first, __i) 区域数据向右移动一个位置,
* __i 数据放在首位。 */
if (__comp(__i, __first)) {
typename iterator_traits<_RandomAccessIterator>::value_type
__val = _GLIBCXX_MOVE(*__i);
_GLIBCXX_MOVE_BACKWARD3(__first, __i, __i + 1);
*__first = _GLIBCXX_MOVE(__val);
} else
/* __i 位置上的数据作为要插入的数据,
* 从右到左(__i 位置开始)逐个数据进行比较排序,直到不满足条件再停下来。 */
std::__unguarded_linear_insert(__i,
__gnu_cxx::__ops::__val_comp_iter(__comp));//直接插入
}
}
template <typename _RandomAccessIterator, typename _Compare>
void
__unguarded_linear_insert(_RandomAccessIterator __last, _Compare __comp) {
typename iterator_traits<_RandomAccessIterator>::value_type
__val = _GLIBCXX_MOVE(*__last);
_RandomAccessIterator __next = __last;
--__next;
while (__comp(__val, __next)) {
*__last = _GLIBCXX_MOVE(*__next);
__last = __next;
--__next;
}
*__last = _GLIBCXX_MOVE(__val);
}
注意:因为 __unguarded_linear_insert 内部 while 循环没有作边界检查,所以 __comp 函数两个参数的比较必须是 v1 > v2 或者 v1 < v2,不能 v1 >= v2 或者 v1 <= v2 这样的,否则程序可能会因为内存越界崩溃.
三.总结:
C++ STL std::sort综合了堆排+快排+插入排序三种排序,限制了递归深度,与分块大小。当递归深度大于2*logn时说明进入了快排陷阱。(如果分块的key值选的好最小深度就是logn)转为使用堆排序。快排/堆排后,可以使得块间相对有序。再使用插入排序加速整个排序结果(插入排序最好的情况下时间复杂度为O(n),擅长对相对有序的数据进行排序)