C++ STL sort底层分析

目录

一、sort的基本用法

二、sort源码分析

2.1整体说明:

2.1.1分析:

2.2快排基准值分析:

2.3基准值选择的优化

2.3.1分析:

2.4 _Med3_unchecked(冒泡分析)

2.4.1分析:

2.5 快排分析

总结



一、sort的基本用法

1.头文件:#include < algorithm>

2.使用标准库:using namespace std

3.函数参数:

#include <algorithm>
 
 template< class RandomIt > void sort( RandomIt first, RandomIt last );

 template< class RandomIt, class Compare > void sort( RandomIt first, RandomIt last, Compare comp );

first和last分别是数组的起始位置和终止位置。

cmp属于扩展用法:你可以传入一个返回值为bool类型的函数,如果结果为true那么比较的两个数就不交换否则交换位置。

实例:

#include < algorithm>
int main()
{
    vector<int>vec = {5,1,6,8,2,7,9};
   /* sort(vec.begin(), vec.end());  //基础用法
    for (auto i : vec)
    {
        cout << i << " ";
    }*/
    sort(vec.begin(), vec.begin() + 3);
    for (auto i : vec)
    {
        cout << i << " ";
    }
}

 扩展:

#include < algorithm>
bool decrease(int first, int second)
{
    return first > second;
}
int main()
{
    vector<int>vec = {5,1,6,8,2,7,9};
   
    sort(vec.begin(), vec.end(),decrease);
    for (auto i : vec)
    {
        cout << i << " ";
    }
}

二、sort源码分析

2.1整体说明:

作者以前只知道使用,从没想过底层原理是什么,以前只认为sort底层就是快排,但是效率比自己写的要高,但实则不然。

首先找到sort:

template <class _RanIt, class _Pr>
_CONSTEXPR20 void sort(const _RanIt _First, const _RanIt _Last, _Pr _Pred) { // order [_First, _Last)
    _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));
}

进入 _Sort_unchecked:

template <class _RanIt, class _Pr>
_CONSTEXPR20 void _Sort_unchecked(_RanIt _First, _RanIt _Last, _Iter_diff_t<_RanIt> _Ideal, _Pr _Pred) {
    // order [_First, _Last)
    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;
        }
    }
}

2.1.1分析:

首先可以看到,sort会先判断数组的长度是否够长(这里的_ISORT_MAX=32)

也就是说小于32直接调用插入排序(_Insertion_sort_unchecked)

然后看下面的_Ideal ,_Ideal = (_Ideal >> 1) + (_Ideal >> 2); // allow 1.5 log2(N) divisions

也就是说最多允许1.5 log2(N)的层次划分,所以快排层数如果超过了,也就是<=0的时候就会执行堆排序。

2.2快排基准值分析:

接着这个auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);Pred是传入规则

快排需要选一个标杆,也就是基准值用于比较,这里并不是单纯的选择最左边的值或者最右边,因为那样选可能会造成On*n的情况,因此这里是一种优化选法;

template <class _RanIt, class _Pr>
_CONSTEXPR20 pair<_RanIt, _RanIt> _Partition_by_median_guess_unchecked(_RanIt _First, _RanIt _Last, _Pr _Pred) {
    // partition [_First, _Last)
    _RanIt _Mid = _First + ((_Last - _First) >> 1); // shift for codegen
    _Guess_median_unchecked(_First, _Mid, _Prev_iter(_Last), _Pred);
    _RanIt _Pfirst = _Mid;
    _RanIt _Plast  = _Next_iter(_Pfirst);

    while (_First < _Pfirst && !_DEBUG_LT_PRED(_Pred, *_Prev_iter(_Pfirst), *_Pfirst)
           && !_Pred(*_Pfirst, *_Prev_iter(_Pfirst))) {
        --_Pfirst;
    }

    while (_Plast < _Last && !_DEBUG_LT_PRED(_Pred, *_Plast, *_Pfirst) && !_Pred(*_Pfirst, *_Plast)) {
        ++_Plast;
    }

    _RanIt _Gfirst = _Plast;
    _RanIt _Glast  = _Pfirst;

    for (;;) { // partition
        for (; _Gfirst < _Last; ++_Gfirst) {
            if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst)) {
                continue;
            } else if (_Pred(*_Gfirst, *_Pfirst)) {
                break;
            } else if (_Plast != _Gfirst) {
                _STD iter_swap(_Plast, _Gfirst);
                ++_Plast;
            } else {
                ++_Plast;
            }
        }

        for (; _First < _Glast; --_Glast) {
            if (_DEBUG_LT_PRED(_Pred, *_Prev_iter(_Glast), *_Pfirst)) {
                continue;
            } else if (_Pred(*_Pfirst, *_Prev_iter(_Glast))) {
                break;
            } else if (--_Pfirst != _Prev_iter(_Glast)) {
                _STD iter_swap(_Pfirst, _Prev_iter(_Glast));
            }
        }

        if (_Glast == _First && _Gfirst == _Last) {
            return pair<_RanIt, _RanIt>(_Pfirst, _Plast);
        }

        if (_Glast == _First) { // no room at bottom, rotate pivot upward
            if (_Plast != _Gfirst) {
                _STD iter_swap(_Pfirst, _Plast);
            }

            ++_Plast;
            _STD iter_swap(_Pfirst, _Gfirst);
            ++_Pfirst;
            ++_Gfirst;
        } else if (_Gfirst == _Last) { // no room at top, rotate pivot downward
            if (--_Glast != --_Pfirst) {
                _STD iter_swap(_Glast, _Pfirst);
            }

            _STD iter_swap(_Pfirst, --_Plast);
        } else {
            _STD iter_swap(_Gfirst, --_Glast);
            ++_Gfirst;
        }
    }
}

这个Mid会返回两个值,[Mid.first,Midsecond]范围内是等于基准值的,具体选出基准值

2.3基准值选择的优化

在_Guess_median_unchecked(_First, _Mid, _Prev_iter(_Last), _Pred);这里

template <class _RanIt, class _Pr>
_CONSTEXPR20 void _Guess_median_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred) {
    // sort median element to middle
    using _Diff        = _Iter_diff_t<_RanIt>;
    const _Diff _Count = _Last - _First;
    if (40 < _Count) { // Tukey's ninther
        const _Diff _Step     = (_Count + 1) >> 3; // +1 can't overflow because range was made inclusive in caller
        const _Diff _Two_step = _Step << 1; // note: intentionally discards low-order bit
        _Med3_unchecked(_First, _First + _Step, _First + _Two_step, _Pred);
        _Med3_unchecked(_Mid - _Step, _Mid, _Mid + _Step, _Pred);
        _Med3_unchecked(_Last - _Two_step, _Last - _Step, _Last, _Pred);
        _Med3_unchecked(_First + _Step, _Mid, _Last - _Step, _Pred);
    } else {
        _Med3_unchecked(_First, _Mid, _Last, _Pred);
    }
}

2.3.1分析:

先判断数量是否小于40个,如果大于那么跳到 _Med3_unchecked(_First, _Mid, _Last, _Pred);

这个函数实际上是一个冒泡排序(下面说),如果小于它定义了两个值 step和Two_step

分别是八分之一个位置,和四分之一个位置,然后分别从first,last,mid三个位置跟他俩组合形成9个位置的数,分别把这9个数进行调整,然后最中间的就是基准值,如下图:

2.4 _Med3_unchecked(冒泡分析)

template <class _RanIt, class _Pr>
_CONSTEXPR20 void _Med3_unchecked(_RanIt _First, _RanIt _Mid, _RanIt _Last, _Pr _Pred) {
    // sort median of three elements to middle
    if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)) {
        _STD iter_swap(_Mid, _First);
    }

    if (_DEBUG_LT_PRED(_Pred, *_Last, *_Mid)) { // swap middle and last, then test first again
        _STD iter_swap(_Last, _Mid);

        if (_DEBUG_LT_PRED(_Pred, *_Mid, *_First)) {
            _STD iter_swap(_Mid, _First);
        }
    }
}

2.4.1分析:

函数前三个参数是比较的数字,最后那个是比较规则(Pred上面提到了,默认是less<>{}

第一个if比较mid是否小于first,如果小则交换,下面两个if也差不多,也就是选出大的,然后换到后面,所以是个冒泡。

2.5 快排分析

 回头看上面的auto _Mid = _Partition_by_median_guess_unchecked(_First, _Last, _Pred);

现在mid的位置已经是基准值了,然后pfirst和plast的位置如下图:

那两个while是用来选等于基准值的数,(那个Pred就是sort传进来的第三个参数,那个规则函数,默认是升序,而那两个while的意思是a不大于b,a也不小于b,那么就是a==b的时候去改变PF和PL的位置,最后PF处是等于基准值PL处不是,因为他一开始就不是。

 下面的两个for循环就是分别是GF从PL出发向右走,GL从PF出发向左走,以GF为例子,如果遇到大于基准值那么continue,如果小于,那么break然后在下面和--GL交换,如果是相等那么和PL交换并更新


总结

        sort函数首先从基本用法上,基础有两个参数,分别传数组的首尾指针,第三个可以传递比较器,可以是函数比较器,也可以是结构体比较器,返回值为bool类型,如果返回true则不交换,否则交换。

        sort底层是根据传入数组的大小来判断使用哪种排序方式,如果长度小于40那么直接用插入排序,否则进入快排,但是如果快排的层数过多使用递归可能会造成栈溢出,因此sort限制递归的层数为1.5logn,当超过限制则会使用堆排序。

        sort快排进行了优化,即对基准值做了优化,如果基准值恰好是最大或者最小值那么快排的复杂度会达到Ologn^2,因此需要选择一个适中的基准值。它将每一段排序的数组分成八段,九个位置,每三个进行一次冒泡排序,再对这三组的中间值进行一次冒泡,得到mid。

        sort中的快排是一半进入递归另一半继续for循环,效率更高。

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BlackMonkeyHH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值