为什么这个能用的快排这么慢?要怎么优化?

为什么我的快排算法最慢? 对一组数据,快排,希尔,堆排,归并排序中,快排最慢?! -- =。=原题主不知哪里去了,来加个例子吧: [图片] 比归并慢了…
关注者
69
被浏览
26,569
登录后你可以
不限量看优质回答私信答主深度交流精彩内容一键收藏

没有给出数据的特点,是没有办法正确分析出算法优劣的。

关于归并和快排的比较,根据英文维基百科的说法:

归并排序最好情况下 O(n),最差情况下 O(n log n),平均 O(n log n);

快速排序最好情况下 O(n log n),最差情况下 O(n^2),平均 O(n log n)。

看起来是快排完败于归排,其实应该归结于实现方法的不同,我认为维基百科的说法存在问题。

根据《算法导论》2.3.1 中对归并算法的解释,归并排序算法是 Θ(n log n),即严格 O(n log n)。

原因在于维基上的 merge 函数,实现时有一个小 trick:当序列完全有序时不必花费线性复杂度合并。这样当然是最好情况 O(n) 的,但相应快排的实现却没有使用这种优化技巧。

所以参考《算法导论》,归并排序 Θ(n log n),快速排序 O(n log n) ~ O(n^2)、期望 Θ(n log n)。但快排常数较小,因此两者在实际应用时,理论差距并不明显,都是对数级排序。

出现题主的情况主要有两个原因:特定的数据类型、对快速排序算法的优化不够。快排一般对于大规模的随机数据效果拔群。

STL 的自带 sort 就是经过大量优化的快速排序,常用的常数优化手段有:

  1. 当整个序列有序时退出算法;
  2. 当序列长度很小时(根据经验是大概小于 8),应该使用常数更小的算法,比如插入排序等;
  3. 随机选取分割位置;
  4. 当分割位置不理想时,考虑是否重新选取分割位置;
  5. 分割成两个序列时,只对其中一个递归进去,另一个序列仍可以在这一函数内继续划分,可以显著减小栈的大小(尾递归):
    function sort ( que[], s, t ) {
        while ( s < t ) {
            int m = partition ( que[], s, t )
            sort ( que[], s, m - 1 )
            s = m + 1
        }
        return
    }
    
  6. 将单向扫描改成双向扫描,可以减少划分过程中的交换次数:
    function partition ( que[], s, t ) {
        int m = random_select ( que[], s, t )
        swap ( que[ m ], que[ t ] )
    
        int p = s, q = t - 1
        while ( p < q ) {
            while ( p < q && que[ p ] <= que[ t ] )
                ++p
            while ( p < q && que[ q ] >= que[ t ] )
                --q
            swap ( que[ p ], que[ q ] )
        }
        swap ( que[ p ], que[ t ] )
        return p
    }
    
  7. 对于网络上的许多非递归、多线程、并行…快排算法,不建议,一般也没必要使用…
  8. 最后一个来自《算法导论》的丧心病狂的思路:选定一个 k,当序列长度小于 k 时,sort 函数直接不作处理返回原序列。整个序列经过这样一次 sort 之后当然不是有序的,此时对这个序列做一次插入排序(因为插入排序在处理 “几乎” 有序的序列时,运行非常快)。根据算导的结论,这样的复杂度是 O(nk + n log(n/k)) 的。(谢谢 @Adder 的证明:其实就是相当于做n/k次k长的插入)>_<