zoukankan      html  css  js  c++  java
  • vs2013 std::sort 分析

    由于之前在debug模式下发现stl的sort简直慢到不能忍,所以自己写了一个sgi的sort,后来发现在release模式下,vs自带的sort快的不行,就研究了下。
    这里有些和sgi-stl相通的东西就简略带过了,详细内容可以看我之前的stl源码的笔记:
    sgi-sort_link
    首先来看下大概的过程:

      1.没有调用到一定深度时,就进行划分并进行递归调用。
      2.如果超过了一定深度时,这个区间改为调用堆排序。(这一部待商榷)
      3.对剩下的小于32长度的区间进行插入排序。
    

    接下来是详细分析:

    1:当我们调用sort(frist,last);时,程序就来到下面的代码,帮忙添加一个参数:less<>()默认用<来比较。

    template<class _RanIt> inline
        void sort(_RanIt _First, _RanIt _Last)
        {   // order [_First, _Last), using operator<
        _STD sort(_First, _Last, less<>());
        }

    2:接着来到,这时会调用真正的sort,也就是_Sort

    template<class _RanIt,
        class _Pr> inline
        void sort(_RanIt _First, _RanIt _Last, _Pr _Pred)
        {   // order [_First, _Last), using _Pred
        _DEBUG_RANGE(_First, _Last);
        _DEBUG_POINTER(_Pred);
        _Sort(_Unchecked(_First), _Unchecked(_Last), _Last - _First, _Pred);
        }

    3:然后进入真正的sort:这里进行说明

    template<class _RanIt,
        class _Diff,
        class _Pr> inline
        void _Sort(_RanIt _First, _RanIt _Last, _Diff _Ideal, _Pr _Pred)
        {   // order [_First, _Last), using _Pred
        _Diff _Count;
            //_Count是计算出来的传入的元素个数,貌似为了安全,还要传入一个元素个数的参数
            //也就是_Ideal
        for (; _ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
            {   // divide and conquer by quicksort
                // 这个循环的原因是为了继续划分
            pair<_RanIt, _RanIt> _Mid =
                _Unguarded_partition(_First, _Last, _Pred);
            _Ideal /= 2, _Ideal += _Ideal / 2;  // allow 1.5 log2(N) divisions
    
            //如果左边区间比较小就递归排序左边的区间,然后继续划分右边的区间
            //否则排序右边,划分左边
            if (_Mid.first - _First < _Last - _Mid.second)
                {   // loop on second half
                _Sort(_First, _Mid.first, _Ideal, _Pred);
                _First = _Mid.second;
                }
            else
                {   // loop on first half
                _Sort(_Mid.second, _Last, _Ideal, _Pred);
                _Last = _Mid.first;
                }
            }
            //划分到区间小于_ISORT_MAX 
    
        //深度太深,改为堆排序。为什么我感觉这是永远不会执行到的因为_ISORT_MAX <_Count
        //不是上一个循环的结束判断条件么
        //而且在release模式下,我设断点都会直接跳过,并不执行if的判断
        //所以目测是不会执行的
        if (_ISORT_MAX < _Count)
            {   // heap sort if too many divisions
            _STD make_heap(_First, _Last, _Pred);
            _STD sort_heap(_First, _Last, _Pred);
            }
        else 
            if (1 < _Count)//到了底层,就调用插入排序了,奇怪的是为什么不学sgi的插入排序,可以
                            //节省很多次的判断。
            _Insertion_sort(_First, _Last, _Pred);  // small
        }
    

    4:下面看看重头戏:

        template<class _RanIt,
        class _Pr> inline
        pair<_RanIt, _RanIt>
            _Unguarded_partition(_RanIt _First, _RanIt _Last, _Pr _Pred)
        {   // partition [_First, _Last), using _Pred
    
        //找到最中间的元素。
        _RanIt _Mid = _First + (_Last - _First) / 2;
    
        //_Median这个函数的作用是把头中尾这三个数进行排序,还会把头中尾附近三个左右的元素进行排序
        //具体不必关心,这里只要知道头中尾三个元素是排好序的。而且_Mid就是关键划分元素
        _Median(_First, _Mid, _Last - 1, _Pred);
    
        //_Pfirst 是左半部分的last,_Plast 是右半部分的first值
        //这里虽然看起来first和last颠倒了,其实这两个值的意思是[_Pfirst, _Plast)区间的元素值
        //全部是_Mid的值("_Mid指向的值"下面简称:mid);
        _RanIt _Pfirst = _Mid;
        _RanIt _Plast = _Pfirst + 1;
    
        //有重复的值就左移或右移动来扩大区间最后达到的效果是这样的[_Pfirst..一堆mid.._Plast)
        //注意_Pfirst和_Plast-1指向的值也是mid
        while (_First < _Pfirst
            && !_DEBUG_LT_PRED(_Pred, *(_Pfirst - 1), *_Pfirst)
            && !_Pred(*_Pfirst, *(_Pfirst - 1)))
            --_Pfirst;
        while (_Plast < _Last
            && !_DEBUG_LT_PRED(_Pred, *_Plast, *_Pfirst)
            && !_Pred(*_Pfirst, *_Plast))
            ++_Plast;
    
        //这两个值分别是未划分的左边部分的尾部:_Glast 以及右半部分的头部:_Gfirst
        _RanIt _Gfirst = _Plast;
        _RanIt _Glast = _Pfirst;
    
        //这时完整的区间是这样的[_First....[_Pfirst(同时也是_Glast)..._Plast)   
        //...._Last)!!注意这里是开区间哈   _Plast也是_Gfirst,为了好看,就写在这了
        for (; ; )
            {   // 划分开始了。。。
            for (; _Gfirst < _Last; ++_Gfirst)//先进行边界判断
            {       
                //从右半边开始,只要_Gfirst指向的值(下面指向的值都去掉“指向的值”这四个字来简称)
                //比_Pfirst(也就是mid)大就一直++
                //直到找到比_Pfirst小的值这样就可以和_Pfirst-1的元素进行交换了
                if (_DEBUG_LT_PRED(_Pred, *_Pfirst, *_Gfirst))
                    ;
                else if (_Pred(*_Gfirst, *_Pfirst))//找到
                    break;
    
                //一个小插曲:如果找到和mid一样大的值就把这个值换到_Plast++的位置来保持
                //[_Pfirst, _Plast)区间全是mid;
                else if (_Plast++ != _Gfirst)//如果两个位置相同就不用换了
                    _STD iter_swap(_Plast - 1, _Gfirst);
            }
    
            //同上面,相应的左区间的操作,最后找到大于mid的位置。
            //只要注意stl右边都是开区间的,_Glast-1才是前面区间的最后一个值
            for (; _First < _Glast; --_Glast)
            {
                if (_DEBUG_LT_PRED(_Pred, *(_Glast - 1), *_Pfirst))
                    ;
                else if (_Pred(*_Pfirst, *(_Glast - 1)))
                    break;
                else if (--_Pfirst != _Glast - 1)
                    _STD iter_swap(_Pfirst, _Glast - 1);
            }
    
            //达到边界就返回一个pair指向mid区间
            //这里的判断是这样的:
            //1:_Glast 是左边未划分区间的最后一个元素的后一个位置所以_First之前
            //未划分_First开始,已经划分。
            //2:_Gfirst 代表右边未划分的第一个元素,而_Last是我们需要划分的区间的最后一个
            //元素的后一个位置,所以如果下面条件成立,[_First ,_Last)划分完成
            if (_Glast == _First && _Gfirst == _Last)
                return (pair<_RanIt, _RanIt>(_Pfirst, _Plast));
    
            //左边全部划分完了,看起来像这样[_First(_Glast,)..._Pfirst..............)
            //这时如果要交换左右两边的元素就需要特殊处理了
            if (_Glast == _First)
                {   // no room at bottom, rotate pivot upward
    
                //情况1:如果_Plast ==_Gfirst说明_Gfirst就是mid区间的last位置
                //这时只需要交换_Pfist和_Gfirst就可以把mid区间右移一格
                if (_Plast != _Gfirst)
                    _STD iter_swap(_Pfirst, _Plast);
                    //情况2:先把一个mid换到最后面,这时_Pfirst指向一个大于mid的值,_Gfirst是指向小于mid的值的。
    
                //++_Plast, 因为中间全是mid的区间要整个后移一格
                ++_Plast;
    
                //情况1:交换_Pfist和_Gfirst相当于把第一个mid值和右边大于mid的值交换
                //情况2:交换_Pfirst,_Gfirst,刚好把小的换到左边,大的换到右边
                _STD iter_swap(_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);
            }
    
            //这个其实就是双基准快排的实现,相对于普通快排最后得到的结果就是把_Mid元素都集中在
            //一起了.
        }

    到此完结,能看到这相信你肯定已经懂了哈,有不对的地方希望大神指出,谢谢!

  • 相关阅读:
    tar命令,vi编辑器
    Linux命令、权限
    Color Transfer between Images code实现
    利用Eclipse使用Java OpenCV(Using OpenCV Java with Eclipse)
    Matrix Factorization SVD 矩阵分解
    ZOJ Problem Set
    Machine Learning
    ZOJ Problem Set
    ZOJ Problem Set
    ZOJ Problem Set
  • 原文地址:https://www.cnblogs.com/boydfd/p/4983127.html
Copyright © 2011-2022 走看看