选择排序、插入排序、归并排序、堆排序、快速排序都是基于关键字比较的排序方法。但是它们各有所长和不足,具体体现在
选择排序
优点:它的优点在于它在排序过程中需要尽可能少内存拷贝和移动,这样如果内存拷贝时间为Tm, 比较关键字大小的时间为Tc。
当Tm >> Tc的时候也就是大块数据进行排序的时候选择排序是个好的算法。
缺点:太稳定的低效使得它并不得宠。 它的复杂度稳定在O(n^2), 这是个致命的缺点。
插入排序
优点: 在输入数据基本有序的情况下,它表现得神一般的快。
缺点: 要进行大量的内存移动,一种超级不稳定的算法。 复杂度为O(n^2)
归并排序
优点: 除了分治算法思想优越以外基本找不到其他漂亮的地方。
缺点: 使用很多的额外存储空间
堆排序
优点: 堆排序的思想是最牛的, 性能稳定而且稳定在nlgn这个时间复杂度上,它可以称为一个神奇的算法。
缺点: 真不好找出它的缺点
快速排序
优点: 快速排序也是一种超级不稳定的排序算法,但是它的最佳表现让其它算法望尘莫及, 平均nlgn的复杂度, 超低的nlgn系数, 使得它备受青睐
缺点: 不稳定性成为它的致命伤, 在输入数据基本有序的情况下它就会退化成为一个O(n^2)的算法。
基于比较的排序方法的最低复杂度为nlgn, 这已经被证明。堆排序和快速排序的平均情况都能达到这个要求,那么这两种排序方法那种更适合于实际应用呢? 曾经有个这方面的争论!直到某一天某个牛人把快速排序改造成为一个稳定在nlgn的复杂度算法的时候,貌似这个争论才停止。
要使得快速排序稳定,那么想办法选取中间值作为KEY就能保证, 如果每次partition的时候都能准确的选择中间值作为划分的key, 快速排序就能得到最佳性能。
假设需要在一个数组中准确的寻找中间值元素, 至少需要O(n)的时间复杂度。 所以精确选择中间值不是一个很好的办法,于是想办法抽样并找到一个可能的中间值成为了一个好的办法。
一个数组抽取A[n], A[1] A[n/2] A[n]三个元素, 先把这三个元素排序, 排完序之后假设它们的中间值就是整个数组的中间值,虽然这个假设过于武断但是它只是能让快速排序稳定在nlgn时间内。 如果数组元素数量较多而且需要更加准确一点的话可以选取数组的9个分割点,找到9个点的中间值并对它们排序。(std::sort就是这样做的)。 如果这种抽样还不够的话,那加上随机抽样来进行中间值的选择。
即使我们使用了抽样取中间值的方法来使得快速排序不会出现最差的表现,但是还是有一定的可能使得每次选择的中间值都不尽人意,使得快速排序出现一个不能忍受的递归深度。 对于N个元素的排序,快速排序最佳的递归深度上lgN, 但是由于每次选择的中间值不是准确的数组中位数,所以lgN的递归深度是不太现实的。 在std::sort中给出了一个让人可以接受的递归深度1.5lgN.
当出现递归深度大于1.5lgN, 又该怎么样来挽救快速排序前面犯下的错误呢? 在std::sort中,大师们判断未排序的数组元素个数, 如果大于等于32采用堆排序,小于32采用插入排序。至于为什么取32这个数就不得而知了。不过肯定是经过无数测试的结果!
走进排序
(1)我们要做的第一步就是搭建一个快速排序框架。
SORT(A, p, q)
IF(p < q)
mid = patition(A, p, q)
SORT(A, p, mid - 1)
SORT(A, mid+1, q)
ENDIF
首先,在数组的p和q划分成为两个部分, 然后再对分开的两个部分又进行排序。
(2)二路划分改为三路划分,以提高重复元素很多的时候的排序效率。
这地方有一点不妥, 假设数组重复元素很多的话, 对数组的二路划分就不太科学了, 所以可以更改成为一个三路划分的办法。 大于key、小于key和等于key。
用下面以个结构来保存中间与key相等的第一个值和最后一个值的下标。 low和high之间的值就是和key 相等的值就不用再排序了。
struct key
{
int low;
int high;
};
SORT(A,p, q)
IF(p<q)
key = patition(A,p,q)
SORT(A, p, key.low)
SORT(A, key.high, q)
ENDIF
(3)控制递归深度小于1.5lgN, 初始值_deep等于N。 递归深度大于1.5lnN之后采用其他排序方法。
SORT(A, p, q, _deep)
IF(p<q && _deep)
_deep/=2; _deep += _deep/2;
key = patition(A,p,q)
SORT(A, p, key.low, _deep)
SORT(A, key.high, q, _deep)
ENDIF
OTHER_SORT_METHOD(A,p,q)
(4) 一般对于小数组排序是使用快速排序不划算,特别是经过递归之后数组元素已经基本有序,这个时候使用直接插入排序会比快速排序效率高。
这个数在qsort中使用8.而在stl::sort中使用32.
在qsort中使用了选择排序,而stl::sort只用的是插入排序。
SORT(A, p, q, _deep)
IF(p<q && _deep&& p - q > 32)
_deep/=2; _deep += _deep/2;
key = patition(A,p,q)
SORT(A, p, key.low, _deep)
SORT(A, key.high, q, _deep)
ENDIF
OTHER_SORT_METHOD(A,p,q)
(5) 在选择其他排序方法的时候。 如果剩下的数组元素大于32那么,采用堆排序。如果小于32那么才与直接插入排序
SORT(A, p, q, _deep)
IF(p<q && _deep&& p - q > 32)
_deep/=2; _deep += _deep/2;
key = patition(A,p,q)
SORT(A, p, key.low, _deep)
SORT(A, key.high, q, _deep)
ENDIF
IF (p-a > 32)
HEAP_SORT(A, p, q)
ELSE
INSERT_SORT(A, p, q)
ENDIF
(6) SORT主体部分再想提高性能就只有想办法更改函数的递归调用。
在qsort中采用了一个栈来实现伪递归,而且这个递归栈大小小于8*sizeof(void*) - 2。在qsort.c中有如下定义:
#define STKSIZ (8*sizeof(void*) - 2)
也就是说在32位的计算机中,栈的深度最多为30, 这是为什么呢?
首先,在最好的情况下会出现最大的栈深度lgN。(N为需要排序的元素个数)
其次,在最坏的情况下会出现最小的栈深度1。
这看上去和想像的不太一样。
事实上这是qsort的一个技术,qsort在处理partition后的两个子数组的时候,选择把元素较多的数组入栈而处理元素较小的子数组。
这样就可以理解了。
假设每次划分的结果小数组里面只有一个元素,那么在小数组的递归处理中不会再有入栈的行为。所以栈深度始终保持为1,但是需要递归N次。
假设每次划分的结果大数组和小数组的元素个数相同, 那么在小数组递归处理的过程中就会有lgN次入栈行为。
qsort的这个做法并没有stl::sort好,它依然存在复杂度为O(n^2)的可能。 stl::sort是先处理大数组再处理小数组,当检查到递归深度大于1.5lgN的时候,选择其他排序方法来挽救。
所以在使用伪递归的时候我们使用STKSIZ = (8*sizeof(void*)) * 1.5; 而且再递归的时候先处理大数组。
(7) 在partition上面下功夫
partition是快速排序是优质产品还是劣质产品的关键。
先说qsort中的三点取中值法。
mid = lo + (size / 2) * width;
if (__COMPARE(lo, mid) > 0) {
swap(lo, mid, width);
}
if (__COMPARE(lo, hi) > 0) {
swap(lo, hi, width);
}
if (__COMPARE(mid, hi) > 0) {
swap(mid, hi, width);
交换之后的mid大于lo而且小于hi, 使得它趋近中间值。
而stl::sort中对大量数组元素的数组采用了9点取值法。
if (40 < _Last - _First)
{ // median of nine
size_t _Step = (_Last - _First + 1) / 8;
_Med3(_First, _First + _Step, _First + 2 * _Step);
_Med3(_Mid - _Step, _Mid, _Mid + _Step);
_Med3(_Last - 2 * _Step, _Last - _Step, _Last);
_Med3(_First + _Step, _Mid, _Last - _Step);
}
else
_Med3(_First, _Mid, _Last);
9点取值,也就是先把数组分为3份, 先取这3份的中间值, 然后再取它们的中间值。
对比qsort和stl:sort
都知道qsort比stl::sort慢,开始很是不理解为什么一个非递归实现的C语言函数会比一个递归实现的C++函数慢。
经过上面的说明我们知道了stl::sort再以下方面比qsort优越
1) 严格控制递归深度绝对保证排序在NlgN的时间复杂度下完成。
2) patition比qsort做的细,让划分的结果更趋近于中间值。
3) 严格的三路划分
另外,还有一个qsort慢的主要原因就是在qsort中定义的swap函数大大的降低了qsort的效率。按字节交换使得可能一次的交换被分成了多次去做。如果用汇编语言先按照机器字长去交换,到最后不满字长按照字节交换的话,qsort效率会高出很多。
一个排序算法的实现:
// swap 交换first 和 second
template<class _Tx>
inline void _swap(_Tx &_first, _Tx &_second)
{
_Tx _temp;
_temp = _first;
_first = _second;
_second = _temp;
}
template<class _Runit>
inline void _iter_swap(_Runit _first, _Runit _second)
{
_swap(*_first, *_second);
}
template<class _Runit>
inline void _mid3(_Runit _first, _Runit _second, _Runit _third)
{
if (*_second < *_first)
_iter_swap(_first, _second);
_iter_swap(_first, _third);
_iter_swap(_third, _second);
template<class _Tx>
struct Partition
{
_Tx first;
_Tx second;
Partition(_Tx f, _Tx s):first(f), second(s){}
Partition(){};
};
template<class _Runit>
inline void _median(_Runit _low, _Runit _mid, _Runit _high)
{
if(_high - _low > 40)
{
// 划分为9份
size_t step = (_high - _low + 1)/8;
_mid3(_low, _low + step, _low + 2 * step);
_mid3(_mid - step, _mid, _mid + step);
_mid3(_high - 2 * step, _high - step, _high);
_mid3(_low + step, _mid, _high - step);
}
else
{
_mid3(_low, _mid, _high);
}
}
template<class _Runit>
inline Partition<_Runit> _partition(_Runit _low, _Runit _high)
{
// 算中位数
_Runit _mid = _low + (_high - _low)/2;
_median(_low, _mid, _high -1);
_Runit _mlo, _mhi;
_mhi = _mid + 1;
// 从中间开始往两边遍历如果发现与关键字相等那么使_mlo 和 _mhi分别向前和向后移动
while (_low < _mlo
&& !(*_mlo < *(_mlo - 1))
&& !(*(_mlo - 1) < *_mlo))
{
--_mlo;
}
&& !(*_mhi < *_mlo)
&& !(*_mlo < *_mhi))
{
++_mhi;
}
_hi = _mhi;
for (;;)
{
// 从_lo开始直到_lo = _low
for (; _low < _lo; --_lo)
{
// 如果小于关键字那么继续循环
if (*(_lo - 1) < *_mlo)
{
}
else if (*_mlo < *(_lo - 1))
{
break;
}
// _iter_swap不存在自己和自己交换时候出现的问题
else
{
_iter_swap(_lo - 1, --_mlo);
}
}
for (; _hi < _high; ++_hi)
{
// 如果大于关键字,继续循环
if (*_mlo < *_hi)
{
}
else if (*_hi < *_mlo)
{
break;
}
else
{
_iter_swap(_hi, _mhi);
++_mhi;
}
}
// partition 完成
if (_lo == _low && _hi == _high)
{
return Partition<_Runit>(_mlo, _mhi);
}
if (_lo == _low)
{
// 然后交换_mlo和_hi。
// 如果_hi 等于_mhi, 那么只需要把_mlo和_hi交换即可
if (_hi != _mhi)
{
_iter_swap(_mlo, _mhi);
}
++_mlo;
++_mhi;
++_hi;
}
else if (_hi == _high)
{
// 如果_mlo - 1 不等于_lo-1,
if (--_mlo != -- _lo)
{
// 交换_mlo 和_lo, 现在_mlo大于关键字
_iter_swap(_mlo, _lo);
}
--_mhi;
_iter_swap(_mlo, _mhi);
}
else
{
_iter_swap(--_lo, _hi ++);
}
}
}
// 插入排序实现
template <class _Runit, class _Ty>
inline void _insertion_sort(_Runit _low, _Runit _high, _Ty*)
{
if (_low == _high)
{
return;
}
_Ty tmp;
_Runit last_sort;
_Runit first_unsort = _low + 1;
_Runit it ;
for (; first_unsort < _high; ++first_unsort)
{
tmp = *first_unsort;
{
for (it= first_unsort; _low < it; --it)
{
*it = *(it-1);
}
*it = tmp;
}
else
{
for (it = first_unsort; tmp < *(it-1); --it)
{
*it = *(it-1);
}
*it = tmp;
}
}
}
inline void insertion_sort(_Runit _low, _Runit _high)
{
_insertion_sort(_low, _high, _Val_type(_low));
}
template<class _Runit>
inline void _heapify(_Runit _low, _Runit _high, _Runit _subroot)
{
// empty or only one element
if (_high <= _low + 1)
{
return;
}
_Runit _maxroot = _low + (_high - _low)/2 - 1;
_Runit left, right, larger;
if (_maxroot < _subroot)
{
return;
}
left = _low + (_subroot - _low)*2 + 1; // left child
right = _low + (_subroot - _low)*2 + 2; // right child
{
larger = _subroot;
}
else
{
larger = left;
}
{
larger = right;
}
{
_iter_swap(larger, _subroot);
_subroot = larger;
goto reverse;
}
template<class _Runit>
inline void _build_heap(_Runit _low, _Runit _high)
{
for (;;)
{
_heapify(_low, _high, root);
if (root == _low)
{
break;
}
--root;
}
}
template<class _Runit>
inline void heap_sort(_Runit _low, _Runit _high)
{
// only one element
if (_low >= _high)
{
return;
}
_build_heap(_low, _high);
for (; tail != _low; --tail)
{
_iter_swap(_low, tail);
_heapify(_low, tail, _low);
}
}
// 快速排序实际主体
//*******************************************************
#define SORT_MAX_SSIZE (sizeof(void*) * 12)
#define CUTOFF 32
template <class _Runit>
inline void xsort(_Runit _low, _Runit _high)
{
Partition<_Runit> part;
_Runit lstack[SORT_MAX_SSIZE];
_Runit hstack[SORT_MAX_SSIZE];
size_t istack[SORT_MAX_SSIZE];
size_t _sp = 0; // 栈指针
size_t _Ideal = _high - _low;
size_t _cnt = 0;
reverse:
// 如果元素个数小于32, 采用插入排序
if ((_cnt = _high - _low) < CUTOFF)
{
if (_cnt > 1)
{
insertion_sort(_low, _high);
}
}
else if (_Ideal == 0)
{
heap_sort(_low, _high);
}
else
{
// 递归深度计算
_Ideal/=2;
_Ideal += _Ideal/2;
part = _partition(_low, _high);
if (part.first - _low < _high - part.second)
{
// 左边入栈
lstack[_sp] = _low;
hstack[_sp] = part.first;
istack[_sp] = _Ideal;
++_sp;
_low = part.second;
}
else
{
// 右边入栈
lstack[_sp] = part.second;
hstack[_sp] = _high;
istack[_sp] = _Ideal;
++_sp;
_high = part.first;
}
}
if (_sp > 0)
{
--_sp;
_low = lstack[_sp];
_high = hstack[_sp];
_Ideal = istack[_sp];
goto reverse;
}
}
/*
* 20090609 by xiaoke, in beijing neusoft
*/