基本算法
快速排序是一种分治的排序算法。它将一个数组分成两个子数组,将两部分独立的排序。
快速排序和归并排序是互补的
归并排序将数组分成两个子数组分别排序,并将有序的子数组归并以将整个数组排序;
快速排序将数组排序的方式是当两个子数组都有序时,整个数组也就自热有序了。
在归并排序中,一个数组被等分为两半;
在快速排序中,切分的位置取决于数组的内容。
示例:
切分元素为K,左半部分为不大于K的元素,右半部分为不小于K的元素;
算法实现:
private static void sort(Comparable[] a,int lo,int hi){
if(hi <= lo) return;
int j = partition(a,lo,hi); // 切分
sort(a,lo,j-1); // 左半部分a[lo...j-1]排序
sort(a,j+1,hi-1: //右半部分a[j+1...hi]排序
}
通过递归的调用切分来排序,关键在于切分,整个过程使得数组满足3个条件
- 对于某个
j
,a[j]
已经排定 a[lo]
到a[j-1]
中的所有元素都不大于a[j]
a[j+1]
到a[hi]
中的所有元素都不小于a[j]
因为切分过程总是能排定一个元素,用归纳法不难证明递归能够正确地将数组排序:如果左子数组和右子数组都是有序的,那么由左子数组、切分元素和右子数组组成的结果数组也一定是有序的。
关于切分方法,一般策略是先随意取a[lo]
作为切分元素,即那个将会被排定的元素,然后从数组的左端开始向右扫描直到找到一个>=
它的元素,再从数组的右端开始向左扫描找到一个<=
它的元素,这两个元素显然是没有排定的,因此交换位置继续。如此继续,则左指针i
的左侧元素都<=
切分元素,右指针j
的右侧元素都>=
切分元素。当两个指针相遇时,a[lo]
与a[j]
交换返回j
即可。
private static int partition(Comparable[] a,int lo,int hi){
//将数组切分为a[lo..i-1],a[i],a[i+1..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);
}
exch(a,lo,j); //将 v = a[j] 放入正确的位置
return j;
}
原地切分
如果使用一个辅助数组可以很容易实现切分,但将切分后的数组复制回去的开销也许会得不偿失。
别越界
如果切分元素是数组中最小或最大的那个元素,小心扫描指针不要跑出数组的边界。
保持随机性
数组元素的顺序是被打乱过的,
终止循环
处理切分元素值有重复的情况
左侧扫描 >=
右侧扫描 <=
终止递归
性能特点
- 快速排序切分方法的内循环会用一个递增的索引将数组元素和一个定值比较。排序算法中难有比这更短小的内循环了
- 比较次数很少
算法改进
1、切换到插入排序
改进快速排序性能的一个简单办法基于以下两点
- 对于小数组,快速排序比插入排序慢
- 因为递归,快速排序的sort()方法在小数组中也会调用自己
因此,在排序小数组时应该切换到插入排序
将if(hi <= lo) return;
替换成
if(hi <= lo + M){ //转换参数M的最佳值和系统相关(一般5~15)
Insertion.sort(a,lo,hi); return;
}
2、三取样切分
。。。。。。。
摘自:《算法·第四版》
漫画版学习:https://blog.csdn.net/csdnnews/article/details/81751022