2.3 快速排序
2.3.1 基本算法
算法描述:快速排序也是分治的排序算法(归并排序也是分治),将数组分成两个部分独立排序,步骤:
1. 打乱
2. 选择切分点(切分点左半部分小于等于切分点,有半部分大于等于切分点)
3. 切分点左半部分排序
4. 切分点有半部分排序
代码如下:
public static void sort(Comparator[] a){
StdRandom.shufflea(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);
}
选择切分点的方法:
1. 先取a[lo]作为切分元素,循环如下操作,直到满足结束条件
2. 从数组左端开始向右扫描直到找到一个大于等于切分点的元素 (用索引i表示)
3. 从数组右端开始向左扫描直到找到一个小于等于切分点的元素 (用索引j表示)
4. 交换找到的这连个元素 (交换索引i和j所指的元素)
5. 结束条件:两个指针相遇(i>=j); 指针到达数组边界(i==hi / j==lo)
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 == lo) break;
if(i>= j) break;
exch(a,i,j);
}
each(a,lo,j);//将v = a[lo]放入正确的位置
return j;
}
2.3.2性能分析:
优势:比较次数少,排序效率最终更依赖切分点的大小
切点:切分不平衡时低效。如:ICI从最小的元素切分,第二次错第二小的元素切分,这样每次调用只移动一个元素,导致一个大子数组需要切分很多次
2.3.3算法改进:
2.3.3.1切换到插入排序
1.对于小数组,快速排序比插入排序慢
2.在排序小数组时切换到插入排序,将
if(hi <= lo) return;
替换为
if(hi <= lo + M) {Insertion.sort(a,lo,hi);return;}
2.3.3.2三取样切分
1.使用子数组中一小部分的中位数来切分数组,代价是需要计算中位数。
2.将选取的元素放在数组末尾作为“哨兵”来去掉partition()中的边界检查。
2.3.3.3三向切分排序
对于含有大量重复元素的数组,算法在重复数字的位置也会进行切分,而这些切分是没有必要的。改进方法是:将数组切分为三部分:小于、等于、大于切分元素。
算法描述:维护一个指针lt使得a[lo,..lt-1]中的元素小于v;一个指针gt使得a[gt+1..hi]中的元素大于v,一个指针i使得a[lt..i-1]中的元素等于v,a[i..gt]中的元素还未确定
1. a[i]小于v,将a[lt]和a[i]交换,将lt和i加1;
2. a[i]大于v,将a[gt]和a[i]交换,将lt和i减1;
3. a[i]等于v,将i加1;
代码如下:
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].comparaTo(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);
}