快速排序
核心思想:
切分过程中排定一个元素,把它放在合适的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]中的元素都还未确定;
- a[i]小于v,将a[lt]和a[i]交换,将lt和i加一;
- a[i]大于v,将a[gt]和a[i]交换,将gt和i减一;
- 等于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中间部分为相等元素;
}