STL的sort()算法

STL的sort()算法

灵魂追问

  1. STL里sort算法用的是什么排序算法?
  2. 数据量大和数据量小都适合用快速排序吗?
  3. 快速排序的时间复杂度不是稳定的nlogn,最坏情况会变成n^2,怎么解决复杂度恶化问题?
  4. 快速排序递归实现时,怎么解决递归层次过深的问题?
  5. 递归过深会引发什么问题?
  6. 怎么控制递归深度?如果达到递归深度了还没排完序怎么办?

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;
        }
    }
}

分析:

  1. 当区间长度小于一定值时,改用插入排序。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;
}

至于为什么选择插入排序,因为经过之前的快排,数据已经相对有序。

  1. 使用_Ideal来控制递归深度,当快排的初始序列是逆序时,复杂度是O(N2)。变量_Ideal初始化为区间长度,每一次划分会执行
 _Ideal = (_Ideal >> 1) + (_Ideal >> 2); // allow 1.5 log2(N) divisions

_Ideal <= 0会进行堆排序,至于为什么选择堆排序,因为复杂度稳定为O(Nlog2N)。

注:

不是所有STL容器都适合sort()。首先,关系型容器底层是红黑树,自动排序所以不需要。其次,栈和优先队列等限制出入口的容器不允许排序。

posted @ 2020-08-19 15:08  kite97  阅读(319)  评论(0编辑  收藏  举报