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