• 科普:std::sort干了什么


    std::sort算是STL中对OIer比较友好的函数了,但你有想过sort是如何保证它的高速且稳定吗?

    正文

    我们首先来到第一层:sort函数

    template<typename _RandomAccessIterator>
    inline void sort(_RandomAccessIterator __first, _RandomAccessIterator __last)
    {
        //申请使用随机访问迭代器 
        __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<_RandomAccessIterator>)
        //申请使用内置的__gnu_cxx::__ops::__iter_less_iter函数 
        __glibcxx_function_requires(_LessThanComparableConcept<typename iterator_traits<_RandomAccessIterator>::value_type>)
        //声明有效区间 
        __glibcxx_requires_valid_range(__first, __last);
        //推锅给std::__sort函数 
        std::__sort(__first, __last, __gnu_cxx::__ops::__iter_less_iter());
    }

    这一层其实也没干什么,只是把锅推给了第二层:__sort函数

    template<typename _RandomAccessIterator, typename _Compare>
    inline void __sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        if (__first != __last)
        {
            //有限制地O(n log n)排序(复杂度优秀,但常数大),将数据几乎有序 
            std::__introsort_loop(__first, __last, std::__lg(__last - __first) * 2, __comp);
            //当数据较有序时,用常数比较小的插入排序(复杂度差,但常数优秀) 
            std::__final_insertion_sort(__first, __last, __comp);
        }
    }

    这里我们就可以见到当年那些大神的神奇操作了:不同的排序方法各司其职,取长补短

    接下来我们分开来看,首先看O(n log n)排序的部分:__introsort_loop函数

    在所有O(n log n)排序中,常数最优秀的当属快速排序,它自然也成了实现O(n log n)排序的首要选择

    当然,直接用快速排序是很可能会被卡的,所以我们要用一个另外的函数兜底

    template<typename _RandomAccessIterator, typename _Size, typename _Compare>
    void __introsort_loop(_RandomAccessIterator __first, _RandomAccessIterator __last, _Size __depth_limit, _Compare __comp)
    {
        //如果排序区间较大,复杂度对效率的影响超过了算法的长度,则使用快速排序 
        while (__last - __first > int(_S_threshold))
        {
            //如果快速排序的层数过大,说明数据对快速排序不友好 
            if (__depth_limit == 0)
            {
                //改用堆排序 
                std::__partial_sort(__first, __last, __last, __comp);
                return;
            }
            --__depth_limit;
            //将数据分为两个集合 
            _RandomAccessIterator __cut = std::__unguarded_partition_pivot(__first, __last, __comp);
            //将后一半递归排序 
            std::__introsort_loop(__cut, __last, __depth_limit, __comp);
            //继续排序前一半 
            __last = __cut;
            //其实这个方法很骚,只会向下增加一次递归,另一层用循环代替 
            //对栈空间的影响比直接两次递归小了不少 
        }
    }

     小声BB:这个参照值的选取太随便了:__unguarded_partition_pivot函数

    template<typename _RandomAccessIterator, typename _Compare>
    inline _RandomAccessIterator __unguarded_partition_pivot(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        //选取中间值 
        _RandomAccessIterator __mid = __first + (__last - __first) / 2;
        //选取first + 1, mid, last - 1的三个位置的中位数作为参照值,并存储在first这个位置上 
        //里面的函数实现太暴力了,全是if(比暴力还暴力),就不拿出来了 
        std::__move_median_to_first(__first, __first + 1, __mid, __last - 1, __comp);
        //快排标准移动,实现如下 
        return std::__unguarded_partition(__first + 1, __last, __first, __comp);
    }
    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;
        }
    }

    如果用快速排序太卡,就改用堆排序:__partial_sort函数

    template<typename _RandomAccessIterator, typename _Compare>
    inline void __partial_sort(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp)
    {
        //建堆 
        std::__heap_select(__first, __middle, __last, __comp);
        //弹堆 
        std::__sort_heap(__first, __middle, __comp);
    }
    
    template<typename _RandomAccessIterator, typename _Compare>
    void __heap_select(_RandomAccessIterator __first, _RandomAccessIterator __middle, _RandomAccessIterator __last, _Compare __comp)
    {
        //建堆 
        //估计这个算法是用堆得到优先级最大的多个元素,所有会有一个空循环 
        std::__make_heap(__first, __middle, __comp);
        for (_RandomAccessIterator __i = __middle; __i < __last; ++__i)
            if (__comp(__i, __first))
                std::__pop_heap(__first, __middle, __i, __comp);
    }
    
    template<typename _RandomAccessIterator, typename _Compare>
    void __make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
        typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;
        
        if (__last - __first < 2)
            return;
        
        const _DistanceType __len = __last - __first;
        _DistanceType __parent = (__len - 2) / 2;
        while (true)
        {
            //从堆底向堆顶更新 
            _ValueType __value = _GLIBCXX_MOVE(*(__first + __parent));
            std::__adjust_heap(__first, __parent, __len, _GLIBCXX_MOVE(__value), __comp);
            if (__parent == 0)
                return;
            __parent--;
        }
    }
    
    template<typename _RandomAccessIterator, typename _Compare>
    void __sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        //pop and pop 
        while (__last - __first > 1)
        {
            --__last;
            std::__pop_heap(__first, __last, __last, __comp);
        }
    }

    当然,STL延续了一贯大常数的“祖宗之法”,调整堆和弹堆都如此复杂

    template<typename _RandomAccessIterator, typename _Distance, typename _Tp, typename _Compare>
    void __push_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __topIndex, _Tp __value, _Compare __comp)
    {
        //向上跳,直到堆稳定为止 
        _Distance __parent = (__holeIndex - 1) / 2;
        while (__holeIndex > __topIndex && __comp(__first + __parent, __value))
        {
            *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __parent));
            __holeIndex = __parent;
            __parent = (__holeIndex - 1) / 2;
        }
        *(__first + __holeIndex) = _GLIBCXX_MOVE(__value);
    }
        
    template<typename _RandomAccessIterator, typename _Distance, typename _Tp, typename _Compare>
    void __adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex, _Distance __len, _Tp __value, _Compare __comp)
    {
        //调整堆 
        //调整方法:先无脑移动到堆底,再向上更新 
        const _Distance __topIndex = __holeIndex;
        _Distance __secondChild = __holeIndex;
        while (__secondChild < (__len - 1) / 2)
        {
            __secondChild = 2 * (__secondChild + 1);
            if (__comp(__first + __secondChild, __first + (__secondChild - 1)))
                __secondChild--;//选择优先级较高的儿子 
            *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first + __secondChild));
            __holeIndex = __secondChild;//向下调整 
        }//如果只有一个儿子 
        if ((__len & 1) == 0 && __secondChild == (__len - 2) / 2)
        {
            __secondChild = 2 * (__secondChild + 1);
            *(__first + __holeIndex) = _GLIBCXX_MOVE(*(__first  + (__secondChild - 1)));
            __holeIndex = __secondChild - 1;
        }
        //向上更新 
        std::__push_heap(__first, __holeIndex, __topIndex, _GLIBCXX_MOVE(__value), __gnu_cxx::__ops::__iter_comp_val(__comp));
    }
    
    template<typename _RandomAccessIterator, typename _Compare>
    inline void __pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last, _RandomAccessIterator __result, _Compare __comp)
    {
        typedef typename iterator_traits<_RandomAccessIterator>::value_type _ValueType;
        typedef typename iterator_traits<_RandomAccessIterator>::difference_type _DistanceType;
        //删除堆顶 
        _ValueType __value = _GLIBCXX_MOVE(*__result);
        *__result = _GLIBCXX_MOVE(*__first);
        //调整堆 
        std::__adjust_heap(__first, _DistanceType(0), _DistanceType(__last - __first), _GLIBCXX_MOVE(__value), __comp);
    }

    当比较有序时,我们就可以用插入排序优化常数:__final_insertion_sort函数

    template<typename _RandomAccessIterator, typename _Compare>
    void __final_insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        if (__last - __first > int(_S_threshold))
        { 
            //先将序列开头排序,作为后面插入排序的预排序区间 
            std::__insertion_sort(__first, __first + int(_S_threshold), __comp);
            //将后面的所有元素排序 
            std::__unguarded_insertion_sort(__first + int(_S_threshold), __last, __comp);
        }
        else
            std::__insertion_sort(__first, __last, __comp);//如果序列较短,就直接排序 
    }

    实现方法也很简单,只是有一点奇怪的操作:

    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)
        {
            //如果插入位置为序列开头,那么直接移动整个序列??? 
            //什么骚操作??? 
            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
                std::__unguarded_linear_insert(__i, __gnu_cxx::__ops::__val_comp_iter(__comp));
        }
    }
    template<typename _RandomAccessIterator, typename _Compare>
    inline void __unguarded_insertion_sort(_RandomAccessIterator __first, _RandomAccessIterator __last, _Compare __comp)
    {
        //十分老实的插入排序 
        for (_RandomAccessIterator __i = __first; __i != __last; ++__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);
    }

     事实上我真的去测试过,在基本有序时,快排真的比插入排序慢(常数太大了)

    总结,sort的实现时这样的:

    sort( *begin, *end )
    {
        __sort( *begin, *end )
        {
            __introsort_loop( *begin, *end, floor )
            {
                if(/*区间长度较大*/)
                {
                    if(/*递归层数过大*/)
                    {
                        //堆排序
                        __partial_sort( *begin, *end ); 
                    } 
                    //选择参照值,并将元素分离 
                    __cut = __unguarded_partition_pivot( *begin, *end ) 
                    //分治 
                    __introsort_loop( *begin, *__cut );
                    __introsort_loop( *__cut, *end );
                }
            }
            __final_insertion_sort( *begin, *end )
            {
                //插入排序 
            }
        }
    }

    懵逼~~~

    看代码看得头晕

    Update

    发现Luogu有个神贴:https://www.luogu.org/discuss/show/112808

    可以发现,如果__last在__first前面,那么就永远不会有__i==__last出现,也就是说,在插入排序时会把__first后面所有的数据全部“排序”,emmm

    这内存一定是中暑了,要不我们……

    STL这鲁棒性太差了

    ——会某人

  • 相关阅读:
    Sql Server 2012 数据库同步方式 (发布、订阅)
    博客搬家通知
    无聊而又劳累的一天
    有没有大神知道国产加密算法SM2的详细介绍
    Android开发环境搭建helloworld程序创建(新手)
    debian下安装东西,遇到两个东西,记录下
    算法:扑克牌相关的一个简单功能示意(纯C实现)
    [51单片机学习笔记FIVE]----独立按键
    [51单片机学习笔记FOUR]----16*16LED点阵
    [51单片机学习笔记THREE]----继电器
  • 原文地址:https://www.cnblogs.com/Iamafool/p/11587612.html
Copyright © 2020-2023  润新知