• stl sort浅析


     编程珠玑在11章讲了插入排序和快速排序,稍后的章节还讲了堆排序

    作者写了多个版本的快速排序,并进行了速度测试,但是最后都干不过stl::sort,stl::sort太给力了

    让我们来分析一下stl::sort的源码

    首先说明下测试代码

    #define  ASIZE 10000000
        int* buf=new int[ASIZE];
        srand(10);
        for (int i=0;i<ASIZE;i++)
        {
            buf[i]=ASIZE-i;
            buf[i]=0;
            buf[i]=rand()*rand();
            buf[i]=rand()%5000;
    
        }

    考虑到快速排序的复杂度在最差情况下是n2,有必要测试在逆序和全0情况下的速度

    首先看看编程珠玑的快速排序最终版

    void fastSort( int* buf,int length )
    {
        if (length<=1)return;
        swap(buf[0],buf[rand()%length]);
        int nMid=buf[0];
        int i=0;
        int j=length;
        while(1)
        {
            do i++; while (buf[i]<nMid&&i<length);
            do j--;while(buf[j]>nMid);
            if(i<j)
                swap(buf[i],buf[j]);
            else break;
        }
        swap(buf[0],buf[j]);
        fastSort(buf,j);
        fastSort(buf+j+1,length-j-1);
    }

    明显的,掉用rand()会有很大开销,可以替换为swap(buf[0],buf[length/2]);

    这样fastSort在我的机器上排序分别需要170 204 1089 651毫秒,可以看出在逆序情况下由于每次都能取到中间点,而且一次交换后就能转为正序,速度是最快的,输入全是0时有与需要频繁swap,速度反而慢,最后两个表明输入的重复数据越多排序越快

    然后这里是stl::sort的代码:

    template<class _RanIt,
        class _Diff> inline
        void _Sort(_RanIt _First, _RanIt _Last, _Diff _Ideal)
        {    // order [_First, _Last), using operator<
        _Diff _Count;
        for (; _ISORT_MAX < (_Count = _Last - _First) && 0 < _Ideal; )
            {    // divide and conquer by quicksort
            _STD pair<_RanIt, _RanIt> _Mid =
                _Unguarded_partition(_First, _Last);
            _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);
                _First = _Mid.second;
                }
            else
                {    // loop on first half
                _Sort(_Mid.second, _Last, _Ideal);
                _Last = _Mid.first;
                }
            }
    
        if (_ISORT_MAX < _Count)
            {    // heap sort if too many divisions
            _STD make_heap(_First, _Last);
            _STD sort_heap(_First, _Last);
            }
        else if (1 < _Count)
            _Insertion_sort(_First, _Last);    // small
        }
    _Unguarded_partition
    template<class _RanIt> inline
        _STD pair<_RanIt, _RanIt>
            _Unguarded_partition(_RanIt _First, _RanIt _Last)
        {    // partition [_First, _Last), using operator<
        _RanIt _Mid = _First + (_Last - _First) / 2;    // sort median to _Mid
        _Median(_First, _Mid, _Last - 1);
        _RanIt _Pfirst = _Mid;
        _RanIt _Plast = _Pfirst + 1;
    
        while (_First < _Pfirst
            && !_DEBUG_LT(*(_Pfirst - 1), *_Pfirst)
            && !(*_Pfirst < *(_Pfirst - 1)))
            --_Pfirst;
        while (_Plast < _Last
            && !_DEBUG_LT(*_Plast, *_Pfirst)
            && !(*_Pfirst < *_Plast))
            ++_Plast;
    
        _RanIt _Gfirst = _Plast;
        _RanIt _Glast = _Pfirst;
    
        for (; ; )
            {    // partition
            for (; _Gfirst < _Last; ++_Gfirst)
                if (_DEBUG_LT(*_Pfirst, *_Gfirst))
                    ;
                else if (*_Gfirst < *_Pfirst)
                    break;
                else
                    _STD iter_swap(_Plast++, _Gfirst);
            for (; _First < _Glast; --_Glast)
                if (_DEBUG_LT(*(_Glast - 1), *_Pfirst))
                    ;
                else if (*_Pfirst < *(_Glast - 1))
                    break;
                else
                    _STD iter_swap(--_Pfirst, _Glast - 1);
            if (_Glast == _First && _Gfirst == _Last)
                return (_STD 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++);
                }
            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);
            }
        }

    stl::sort主要进行了一下优化:

    1 消除尾递归

    2 在递归深度超过xlogn之后便使用堆排序,堆排序在最差情况下也有nlogn的速度,但是堆排序的常系数比较大,特别是在输入有序的情况下,所以只能作为辅助

    3 在排序小数据时使用插入排序,插入排序实现简单,在n很小时要快于快速排序,现在这个边界值是32

    4 优化切分方法_Unguarded_partition返回的是一个pair<int,int>,它会把所有跟mid相同的元素都移动到中间,可以加快递归速度,输入的重复元素越多这个方法就越有效

      但是从_Unguarded_partition内部可以看出,最坏情况下这个方法会进行许多多余的swap操作

    stl::sort进行的各种优化主要是为了防止出现最坏情况,n2的运行速度是不能容忍的

    stl::sort的测试:222 11 1227 616

    再输入是逆序时,快速排序只要进行一次交换就可以将数组变为有序的,接下来的操作也就变得很快了,此时stl::sort进行的优化反而拖慢了速度

    在输入随机而且重复元素比较少时fastSort略快

    但是可以看出重复元素越多stl::sort就越快

     如果不使用stl的_Unguarded_partition优化,直接使用fastSort的切分方法:

    const int g_cut=32;
    void fastSort2( int* buf,int length,int depth)
    {
        while(1)
        {
            if (length<=1)return;
            else if(length<g_cut)
            {
                insertSort(buf,length);
                return;
    
            }else if(depth<1)
            {
                heapSort(buf,length);
                return;
            }
            depth=depth*3/4;
            swap(buf[0],buf[length/2]);
            int nMid=buf[0];
            int i=0;
            int j=length;
    
            while(i<j)
            {
                do i++; while (buf[i]<nMid&&i<length);
                do j--;while(buf[j]>nMid);
                if(i<j&&buf[i]!=buf[j])
                    swap(buf[i],buf[j]);
            }
            swap(buf[0],buf[j]);
            if(j>length-j)
            {
                fastSort2(buf+j+1,length-j-1,depth);
                length=j;
            }else
            {
                fastSort2(buf,j,depth);
                buf+=j+1;length=length-j-1;
            }
        }
         
    }

    速度分别为:156 164 988 654

    可以看出stl::sort对中间点的优化效果并不十分明显:优化了重复元素较多情况下的速度,但是降低了对随机元素的排序速度

  • 相关阅读:
    poj 1222 EXTENDED LIGHTS OUT (高斯消元 )
    poj 2187 Beauty Contest (凸包: 最远点对,最长直径 , 旋转卡壳法)
    poj 1408 Fishnet (几何:线段相交 + 叉积 求面积 )
    poj 1228 Grandpa's Estate ( 凸包 )
    高斯消元 模版
    poj 1830 开关问题 (高斯消元 )
    poj 1113 Wall (凸包:周长)
    旋转卡壳算法
    poj 1681 Painter's Problem (高斯消元 )
    字符串相关处理
  • 原文地址:https://www.cnblogs.com/mightofcode/p/2760952.html
Copyright © 2020-2023  润新知