1. 程式人生 > 實用技巧 >STL的sort()演算法

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()。首先,關係型容器底層是紅黑樹,自動排序所以不需要。其次,棧和優先佇列等限制出入口的容器不允許排序。