zoukankan      html  css  js  c++  java
  • 科普: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这鲁棒性太差了

    ——会某人

  • 相关阅读:
    Windows Store App 主题动画
    Windows Store App 过渡动画
    Windows Store App 控件动画
    Windows Store App 近期访问列表
    Windows Store App 文件选取器
    Windows Store App 访问应用内部文件
    Windows Store App 用户库文件分组
    Windows Store App 获取文件及文件夹列表
    Windows Store App 用户库文件夹操作
    Windows Store App 用户库文件操作
  • 原文地址:https://www.cnblogs.com/Iamafool/p/11587612.html
Copyright © 2011-2022 走看看