基于比较的排序总结-结合qsort&&std::sort分析

选择排序、插入排序、归并排序、堆排序、快速排序都是基于关键字比较的排序方法。但是它们各有所长和不足,具体体现在

选择排序
优点:它的优点在于它在排序过程中需要尽可能少内存拷贝和移动,这样如果内存拷贝时间为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效率会高出很多。

一个排序算法的实现:

 
排序的一个可以在实际中应用的源程序, 结合了std::sort和qsort的优点, 使用c++模板实现
总之吾不认为能做的可能比库函数更快、但是可以学习库函数先进的思想和技巧.
实现(C++ template)

//
// swap 交换first 和 second
template<class _Tx>
inline void _swap(_Tx &_first, _Tx &_second)
{
        _Tx _temp;
        _temp = _first;
        _first = _second;
        _second = _temp;
}
// iter_swap 交换first 和 second迭代器(指针)指向的值
template<class _Runit>
inline void _iter_swap(_Runit _first, _Runit _second)
{
        _swap(*_first, *_second);
}
// 3个数排序
template<class _Runit>
inline void _mid3(_Runit _first, _Runit _second, _Runit _third)
{
        if (*_second < *_first)
                _iter_swap(_first, _second);
        if (*_third < *_first)
                _iter_swap(_first, _third);
        if (*_third < *_second)
                _iter_swap(_third, _second);
}     
  
// 存放partition结果的结构
template<class _Tx>
struct Partition
{
        _Tx first;
        _Tx second;
        Partition(_Tx f, _Tx s):first(f), second(s){}
        Partition(){};
};
// 9点算近似中位数
template<class _Runit>
inline void _median(_Runit _low, _Runit _mid, _Runit _high)
{
        if(_high - _low > 40)
        {
                // 划分为9份
                size_t step = (_high - _low + 1)/8;
                // 3份为一组分别求中位数
                _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;
        // sort [_low, high)
        _median(_low, _mid, _high -1);
        _Runit _lo, _hi;
        _Runit _mlo, _mhi;
        _mlo = _mid;
        _mhi = _mid + 1;

        // 从中间开始往两边遍历如果发现与关键字相等那么使_mlo 和 _mhi分别向前和向后移动
        while (_low < _mlo
                && !(*_mlo < *(_mlo - 1))
                && !(*(_mlo - 1) < *_mlo))
        {
                --_mlo;
        }
        while(_mhi < _high
                && !(*_mhi < *_mlo)
                && !(*_mlo < *_mhi))
        {
                ++_mhi;
        }
        // 现在 [_mlo, _mhi)等于关键字key
        _lo = _mlo;
        _hi = _mhi;
        // partition
        for (;;)
        {
                // 从_lo开始直到_lo = _low
                for (; _low < _lo; --_lo)
                {
                        // 如果小于关键字那么继续循环
                        if (*(_lo - 1) < *_mlo)
                        {
                        }
                        // 如果大于关键字, 跳出循环, 准备和[_mhi, _high)中的一个小于关键字的交换
                        else if (*_mlo < *(_lo - 1))
                        {
                                break;
                        }
                        // 如果等于关键字, 与_mlo - 1交换(因为*(_mlo - 1) 小于关键字), 因为使用了
                        // _iter_swap不存在自己和自己交换时候出现的问题
                        else
                        {
                                _iter_swap(_lo - 1, --_mlo);
                        }
                }
                // 从_hi 开始直到_hi = high
                for (; _hi < _high; ++_hi)
                {
                        // 如果大于关键字,继续循环
                        if (*_mlo < *_hi)
                        {
                        }
                        // 如果小于关键字, 跳出循环, 准备和_lo交换
                        else if (*_hi < *_mlo)
                        {
                                break;
                        }
                        // 如果等于关键字, 与_mhi交换
                        else
                        {
                                _iter_swap(_hi, _mhi);
                                ++_mhi;
                        }
                }
                // 经过以上, 循环不变式保持[_mlo, _mhi)等于关键字

                // partition 完成
                if (_lo == _low && _hi == _high)
                {
                        return Partition<_Runit>(_mlo, _mhi);
                }
                // 如果_lo 等于_low那么[_low, _mlo)都小于等于关键字, 如果_hi也小于关键字, 那么就会出现前面的空间不够,这样需要移动关键字
                if (_lo == _low)
                {
                        // 如果_hi 不等于_mhi, 那么_mhi肯定大于关键字, 此时交换_mlo和_mhi,这样_mlo 大于关键字, _mhi等于关键字
                        //                然后交换_mlo和_hi。
                        // 如果_hi 等于_mhi, 那么只需要把_mlo和_hi交换即可
                        if (_hi != _mhi)
                        {
                                _iter_swap(_mlo, _mhi);
                        }
                        _iter_swap(_mlo, _hi);
                        ++_mlo;
                        ++_mhi;
                        ++_hi;
                }
                // 如果_hi 等于 _high 那么, [_mhi,high)都大于关键字
                else if (_hi == _high)
                {
                        // 如果_mlo - 1 不等于_lo-1,
                        if (--_mlo != -- _lo)
                        {
                                // 交换_mlo 和_lo, 现在_mlo大于关键字
                                _iter_swap(_mlo, _lo);
                        }
                        // 和_mhi交换
                        --_mhi;
                        _iter_swap(_mlo, _mhi);
                }
                // 两边都有位置那么直接交换_lo和_hi
                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;
                if (*first_unsort < *_low)
                {
                        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;
                }
               
        }
}
// 插入排序
template <class _Runit>
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;
reverse:
        if (_maxroot < _subroot)
        {
                return;
        }

        left = _low + (_subroot - _low)*2 + 1; // left child
        right = _low + (_subroot - _low)*2 + 2; // right child
        if (*left < *_subroot)
        {
                larger = _subroot;
        }
        else
        {
                larger = left;
        }
        if (right < _high && *larger < *right)
        {
                larger = right;
        }
        if (larger != _subroot)
        {
                _iter_swap(larger, _subroot);
                _subroot = larger;
                goto reverse;
        }
}
// 建堆
template<class _Runit>
inline void _build_heap(_Runit _low, _Runit _high)
{
        _Runit root = _low + (_high - _low)/2 - 1;
        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
        _build_heap(_low, _high);
        _Runit tail = _high - 1; // heap tail
        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;
         // 伪递归栈 容许最多递归1.5lgN N < 2^32
        _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);
                }
               
        }
        // 如果元素个数大于32, 但是递归深度大于1.5lgN, 采用堆排序
        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;
                }
                goto reverse;
        }
        // 排序完成如果栈为空返回,栈不为空继续伪递归
        if (_sp > 0)
        {
                --_sp;
                _low = lstack[_sp];
                _high = hstack[_sp];
                _Ideal = istack[_sp];
                goto reverse;
        }
}

/*
* 20090609 by xiaoke, in beijing neusoft
*/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值