快速排序
前情回顾
前面介绍了几种简单的排序方法
快速排序特点
1.实现简单
2.适用于各种不同的输入数据
3.比其他排序算法快得多
4.原地排序(只需要一个很小的辅助栈)
5.所需时间和NlgN成正比。
算法思想
将一个数组分成两个子数组,左子数组和右子数组。当两个子数组分别有序后,整个数组就完成排序。
public static void sort(Comparable[] a){
StdRandom.shuffle(a); //将原数组随机打乱,消除算法对输入的依赖
sort(a,0,a.length-1);
}
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); //将右半部分a[j+1..hi]排序
}
快速排序递归地将子数组a[lo…hi]排序,先用partition()方法将a[j]放到一个合适的位置(也就是这个元素最终的位置)然后再递归调用将其他位置的元素排序。,
那如何将一个数组分成两个子数组呢?依据哪个元素进行划分,也就是上面代码中的partition方法。这个过程使得数组满足下面三个条件:
- 对某个j,a[j]已经排定
- a[lo]到a[j-1]中的所有元素都不大于a[j];
- a[j+1]到a[hi]中的所有元素都不小于a[j]。
**快速排序其实就是通过递归调用这个partition切分方法来排序的。
一般策略是
- 随意选择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; //a[lo..j-1]<=a[j]<=a[j+1..hi]达成
}
改进的快速排序
如果待排序数组中有大量相同的元素,则可将数组分为三部分
1.左子数组,元素均小于比较元素v
2.中间子数组,元素均等于比较元素v
3.右子数组,元素均大于比较元素v
public static void sort(Comparable[] a){
StdRandom.shuffle(a); //将原数组随机打乱,消除算法对输入的依赖
sort(a,0,a.length-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].compareTo(v);
if(cmp<0) exch(a,lt++,i++);
else if(cmp >0) exch(a,i,gt--);
else i++;
}//现在是a[lo..lt-1]<v=a[lt..gt]<a[gl+1..hi]成立
sort(a,lo,lt-1);
sort(a,gt+1,hi);
}
这段代码的切分能够将和切分元素相等的元素归位,这样它们就不会被包含在递归调用处理的子数组中了。对于存在大量重复元素的数组,这种方法比标准的快速效率高得多。
完整代码(java)
/**
* @author zhangjinglong
* @date 2019-06-20-23:04
* 快速排序
*/
public class Quick {
public static void sort(Comparable[] a){
StdRandom.shuffle(a); //将原数组随机打乱,消除算法对输入的依赖
sort(a,0,a.length-1);
}
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); //将右半部分a[j+1..hi]排序
}
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; //a[lo..j-1]<=a[j]<=a[j+1..hi]达成
}
private static boolean less(Comparable v,Comparable w){
return v.compareTo(w)<0;//升序
}
private static void exch(Comparable[] a,int i,int j){
//交换数组中的两个元素
Comparable t=a[i];
a[i]=a[j];
a[j]=t;
}
private static void show(Comparable[] a){
//在单行中打印数组
for (int i = 0; i < a.length; i++) {
System.out.print(a[i]+" ");
}
System.out.println();
}
public static boolean isSorted(Comparable[] a){
//测试数组元素是否有序
for (int i = 1; i < a.length; i++) {
if(less(a[i],a[i-1])) return false;//如果数组中有相邻的任意两个元素逆序,则返回false
}
return true;
}
public static void main(String[] args) {
String[] s={"I","A","B","Z","C","F"};
sort(s);
assert isSorted(s);//用于检验我们的排序算法是否正常运行
show(s);
}
}