算法和数据结构(15)快速排序

快速排序

核心思想:

切分过程中排定一个元素,把它放在合适的index中(即该数组排序后它应该有的index)。随后递归调用,直到整个数组排好序;

注意,该元素a[lo],左边<=a[lo],右边>=a[lo];

要实现这个,需要实现这个切分的方法,把a[lo]作为切分元素,即那个将要被排定的元素,然后从数组左端开始向右扫描,直到找到一个>=它的元素,再从数组右边开始向左扫描,直到一个<=它的元素,显然这两个元素没有排定,交换这两个元素的位置,如此继续,当两指针相遇时,我们只需要将a[lo]和当前位置交换,并返回两指针相遇时的index即可;

注意:该思想还有几个细节,可能导致实现错误或者影响性能,稍后会改进;

快速切分的实现:

private static int partition(Comparable a[],int lo,int hi){
  int i = lo , j = hi+1;
  Comparable v = a[lo];
  while(true){
    while(less(a[++i],v)) if(i==hi) break;
    while(less(v,a[--j])) if(j==low) break;
    if(i>=j) break;
    exch(a,i,j);
  }
  exch(a,lo,j);
  return j;
}

如此,利用partition方法,即可将a[lo]放入它的合适的index中;

快速排序总算法:

public static void sort(Comparable[] a){
  sort(a,0,a.length-1);
}
public static void sort(Comparable[] a,int lo, int hi){
  if(hi<=lo) return;
  int j = partition(a,lo,hi);
  sort(a,lo,j-1);
  sort(a,j+1,hi);
}

总结:

快速排序,可能是应用最广泛的排序算法了,快速排序流行的原因是它实现简单,只需要很小的辅助栈,且长度N的数组排序所需的时间和NlogN成正比,其他算法不同时具备这两种特性;

缺点:非常脆弱,在实现时要非常小心才能避免低劣的性能;

例如:在切分不平衡时,这个程序可能非常低效,如第一次选择的a[lo]为最小元素时,第二次从第二小元素开始切分,如此这般,每次调用只能移动一个元素,这回导致大数组切分很多次,我们要在在快速排序前将数组随机排序,主要为了避免这种情况的发生,它能够使产生糟糕的切分的可能性降低极低;

故切分前添加代码

StdRandom.shuffle(a);

算法改进

切换到插入排序

对于小数组来说,快速排序比插入排序慢;详情参考
插入排序

故可以将

if(lo>=hi) return;
//替换为
if(lo+M>=hi) {Insertion.sort(a,lo,hi); return;}

参数M的最佳值一般选择5~15之间;

熵最优的排序

背景:实际应用中经常会出现含有大量的重复元素,例如大量人员资料按生日 || 性别 进行排序;这些情况下,快速排序的性能尚可,但还有巨大的改进空间,例如一个元素全部重复的子数组就不要排序了,但是现有的算法还会继续将它切分为更小的数组,在有大量的重复数组的情况下,这就有了很大的改进潜力;

思想:将这个数组切分为三份,分别对应小于,等于,大于切分元素的数组元素,但是这种切分的实现比我们目前快排的二分法更复杂,人们为解决它想出了不同的办法,这也是Dijkstra的荷兰国旗问题上引发的一道经典的编程训练题,因为这就好像用三种可能的主键值将素组排序一样,这三种主键值对应着荷兰国旗的三种颜色🇳🇱;

Dijkstra的解法:

从左到右遍历数组一次,维护一个指针lt使得a[lo…lt-1]中的元素小于v,一个指针gt使得a[gt+1…hi]中的元素都大于v;一个指针i使得a[lt…gt]中的元素都还未确定;

  1. a[i]小于v,将a[lt]和a[i]交换,将lt和i加一;
  2. a[i]大于v,将a[gt]和a[i]交换,将gt和i减一;
  3. 等于v,i加一;

上述这些操作,都会保证元素不变,且gt-i的值缩小

private static void sort(Comparable a,int lo ,int hi){
  if(hi<=lo) return;
  int lt = lo ,i = lo+1,gt = hi;
  Comparable v = a[lo];
  //三个指针共同维护这个数组
  while(i<=gt){
    int cmp = a[i].compareTo(v);
    if(cmp<0) exch(a,lt++,i++);
    else if(cmp>0) exch(a,i,gt--);
    else i++;
  }
  sort(a,lo,lt-1);
  sort(a,gt+1,hi);
  //lt--gt中间部分为相等元素;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值