Note on Quick Sort

如果你想学习、提高改进QuickSort的实现,建议先看   Engineering a sort function; Jon Bentley and M. Douglas McIlroy; Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.

 

算法的基本思想:
快速排序的基本思想是基于分治策略的。对于输入的子序列L[p..r],如果规模足够小则直接进行排序,否则分三步处理:
    * 分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。
    * 递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。
    * 合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算 L[p..r]就已排好序。
这个解决流程是符合分治法的基本步骤的。因此,快速排序法是分治法的经典应用实例之一。

 

    C代码

void quick_sort(int *a, int n)
{
   int m;
   if (n <= 1) return;

   m = patition(a, n);

   quick_sort (a, m);
   quick_sort (a + m + 1, n - m - 1);
}

选择Partition元素:

1.很多实现用第一个或最后一个作为pivot,这种在大多数情况还是有很好的性能,但是对于一些特殊输入如:大部分已有偏序的序列,将是性能最坏的时候。

2.随机选择一个数做pivot,相比已方法1,可以干掉特殊系列,不过随机导入的大多数情况只能有平均性能。

3.尽量选择偏序后的中间元素,使得算法逼近 nlogn: 这里有Tukey’s ‘ninther’, the median of the medians of three samples、 each of three elements.

 

Partition的方法:要考虑如何处理与pivot相等的元素?和性能有很大关系。

1. lemuto's Partition

lomuto partitioninng

My Code:

int patition_lomuto(int *a, int n)
{
    int i, m;

    swap (a + rand () % n, a + n - 1);
    for (i = 0, m = 0; i < n - 1; i++)
       if (a[i] <= a[n - 1]) {
           swap (a + i, a + m);
           m = m + 1;
       }
    swap (a + m, a + n - 1);
   
    return m;
}

这里我把pivot放在最后一个元素,不过是一样的。还有这里的采用上述提到的方法2 来选择pivot.

 

2. 用两个指针大部分算法提到的,相比于方法1,比较的次数下降,效率有明显的提高

two pointer partitioning

 

 

int patition_twoptr(int *a, int n)
{
    int i, j;
   
    swap(a, a + rand() % n);
    i = 0; j = n;
    for (;;){
        do i++; while (i < n && a[i] < a[0]);
        do j--; while (a[j] > a[0]);
        if (i > j) break;
        swap(a+i, a+j);
    }
    swap(a, a+j);
   
    return j;
}

 

3. 对于两个指针优化,妥善处理了与pivot相等的元素的问题,参考经典缺陷2

经典的有:Dijkstra’s ‘Dutch National Flag’

Dijkstra

但是其编码复杂,而且要达到好效率似乎要花一翻功夫。

partition split end

void quick_sort2(int *x, int n)
{
   int a, b, c, d, l, h, s, v;
   if (n <= 1) return;

   v = x[rand () % n];
   a = b = 0;
   c = d = n - 1;
   for (;;) {
      while (b <= c && x[b] <= v) {
         if (x[b] == v) swap (x + a++, x + b);
         b++;
      }
      while (c >= b && x[c] >= v) {
         if (x[c] == v) swap (x + c, x + d--);
         c--;
      }
      if (b > c) break;
      swap (x + b++, x + c--);
   }

   s = min (a, b - a);
   for (l = 0, h = b - s; s; s--)
      swap (x + l++, x + h++);

   s = min (n - 1 - d, d - c);
   for (l = b, h = n - s; s; s--)
      swap (x + l++, x + h++);

   quick_sort2 (x, b - a);
   quick_sort2 (x + n - (d - c), d - c);
}

 

著名的性能缺陷:

1. Scowen’s ‘Quickersort’ in Version Seventh of Unix System, took n2 comparisons to sort an ‘organpipe’ array of 2n integers: 123..nn.. 321.

2. Quickersort in Berkeley 1983 consume quadratic time on arrays that contain a few elements repeated many times—in particular arrays of random zeros and ones.

 

至于Tukey’s ‘ninther’ method, 以及更多的性能优化,请google.

关于qsort的库实现的亮点,简单的提及下,后面我也粘贴了个参考贴。

1.库中用了一个很精练的Stack避免了递归调用

2.用了median-of-three decision tree来选择pivot元素

3.在元素个数小于某MAX_THRESH 时用insertsort,大于MAX_THRESH时用quickSort.

PS: 1,2 两点的改进处于大师Ken Thompson’s之手, 有人不认识Ken? C 和Unix的创始人

 

 

参考材料:

And Here is a video by Jon Bentley: http://www.youtube.com/watch?v=aMnn0Jq0J-E

http://en.wikipedia.org/wiki/Quick_sort

 

P.S.:这里有搜索到站内的对于qsort库实现的分析:

http://blog.youkuaiyun.com/ghame/archive/2006/05/18/744547.aspx

http://blog.youkuaiyun.com/ghame/archive/2006/05/20/746150.aspx

http://blog.youkuaiyun.com/ghame/archive/2006/05/21/747911.aspx

 

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值