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对中间点的优化效果并不十分明显:优化了重复元素较多情况下的速度,但是降低了对随机元素的排序速度

 

posted @ 2012-11-09 11:11  mightofcode  阅读(687)  评论(0编辑  收藏  举报