对于包含n个数的输入数组来说,快速排序是一种最坏情况时间复杂度为Θ(n^2) 的排序算法。虽然最坏情况时间复杂度很差,但是快速排序通常是实际排序应用中最好的选择,因为它的平均性能很好:它的期望时间复杂度是Θ(n lgn),而且Θ(n lgn)中隐含的常数因子非常小。另外,它还能够进行原址排序,甚至在虚存环境中也能很好的工作。
快速排序的描述
与归并排序一样,快速排序也使用了分治思想。下面是对一个典型的子数组A[p…r] 进行快速排序的三步分治过程:
- 分解: 数组A[p…r]被划分为两个(可能为空)子数组A[p…q-1] 和A[q+1…r],使得A[p…q-1]中的每一个元素都小于等于A[q],而A[q]也小于等于A[q+1…r]中的每个元素。其中,计算下标q也是划分过程的一部分。
- 解决: 通过递归调用快速排序,对子数组A[p…q-1]和A[q+1…r]进行排序。
- 合并: 因为子数组都是原址排序的,所以不需要合并操作:数组A[p…r]已经有序。
下面的伪程序实现快速排序:
QUICKSORT(A,q,r)
1. if p<r
2. q=PARTITION(A,p,r)
3. QUICKSORT(A,p,q-1)
4. QUICKSORT(A,q+1,r)
为了排序一个数组A的全部元素,初始调用QUICKSORT(A,1,A.length)。
数组的划分
算法的关键部分是PARTITION过程,它实现了对子数组A[p…r] 的原址排序。
PARTITION(A,p,r)
1. x=A[r]
2. i=p-1
3. for j=p to r-1
4. if A[j]<x
5. i=i+1
6. exchage A[i] with A[j]
7. exchage A[i+1] with A[r]
8. return i+1
下图显示了PARTITON如何在一个包含8个元素的数组上进行操作的过程。PARTITION总是选择一个x=A[r]作为主元(pivot element),并围绕它来划分子数组A[p…r]。
随着程序的执行,数组被划分成4个(可能有空的)区域。在第3~6行的for循环的每一轮迭代的开始,每一个区域都满足一定的性质,如下图所示。我们将这些性质作为循环不变量:
在第3~6行循环体的每一轮迭代开始时,对于任意数组下标k,有:
- 若p≤k≤i,则A[k]≤x
- 若i+1≤k≤j-1,则A[k]>x
- 若k=r,则A[k]=x
java代码实现:
import java.util.Scanner;
class quicksort{
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("请输入需要快速排序的数组规模:");
int[] a= new int[scanner.nextInt()];
System.out.print("请输入排序的数组元素:");
for(int i=0;i<a.length;i++){
a[i]=scanner.nextInt();
}
quicksort(a);
System.out.print("排序后的结果:");
for(int i=0;i<a.length;i++){
if(i==0){
System.out.print("["+a[i]+",");
}else if(i==a.length-1){
System.out.print(a[a.length-1]+"]");
}else{System.out.print(a[i]+",");}
}
}
public static void quicksort(int[] a){
if(a==null||a.length<2){ //判断数组是否为空以及数组元素个数
return;
}
quicksort(a,0,a.length-1);
}
public static void quicksort(int[] a,int p,int r){
if(p<r){
int q=PARRTION(a,p,r);
quicksort(a,p,q-1);
quicksort(a,q+1,r);
}
}
public static int PARRTION(int[] a,int p,int r){
int x=a[r];
int i=p-1;
for(int j=p;j<r;j++){
if(a[j]<=x){
i=i+1;
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
int temp=a[r];
a[r]=a[i+1];
a[i+1]=temp;
return i+1;
}
}
测试:
快速排序的随机化版本
根据上面的过程,我们会发现快速排序的运行时间依赖于划分是否平衡,而平衡与否又依赖于用于划分的元素。如果划分是平衡的,那么快速排序算法性能与归并排序一样。如果划分是不平衡的,那么快速排序的性能就接近于插入排序了。所以,我们要去寻找一种快速排序的平均性能。
那在讨论平均情况性能的时候,我们的前提假设是:输入数据的所有排列都是等概率的。但是在实际工作中,这个假设并不会总是成立。为了满足等概率的情况,有时我们可以通过在算法中引入随机性,从而使得算法对于所有的输入都能获得较好的期望性能。很多人都选择随机化版本的快速排序最为大数据输入情况下的排序算法。
对于快速排序,我们可以通过显式地对输入进行重新排列,使得算法实现随机化。但如果采用一种称为随机抽样的随机化技术,那么可以使得分析变得更加简单。与始终采用A[r]作为主元的方法不同,随机抽样是从子数组A[p…r]中随机选择一个元素作为主元。为达到这一目的,首先将A[r]与从A[p…r]中随机选出的一个元素交换。通过对序列 p,…,r 的随机抽样,我们可以保证主元元素x=A[r]是等概率地从子数组的r-p+1个元素中选取的。因为主元元素是随机选取的,我们期望是在平均情况下,对输入数组的划分是比较均衡的。
对PARTITION和QUICKSORT的代码改动非常小。在新的划分程序中,我们只是在真正进行划分前进行一次交换:
RANDOMIZED_PARTITION(A,p,r)
1. i=RANDOM(p,r)
2. exchage A[r] with A[i]
3. return PARTITION(A,p,r)
新的快速排序不再调用PAARTITION,而是调用RANDOMIZED_PARTITION:
RANDOMIZED_QUICKSORT
1. if p<r
2. q=RANDOMIZED_PARTITION(A,p,r)
3. RANDOMIZED_QUICKSORT(A,p,q-1)
4. RANDOMIZED_QUICKSORT(A,q+1,r)
java代码实现:
import java.util.Random;
import java.util.Scanner;
class Random_Quicksort{
public static Random random = new Random();
public static void main(String[] args){
Scanner scanner = new Scanner(System.in);
System.out.print("请输入需要随机快速排序的数组规模:");
int[] a= new int[scanner.nextInt()];
System.out.print("请输入排序的数组元素:");
for(int i=0;i<a.length;i++){
a[i]=scanner.nextInt();
}
random_quicksort(a);
System.out.print("随机排序后的结果:");
for(int i=0;i<a.length;i++){
if(i==0){
System.out.print("["+a[i]+",");
}else if(i==a.length-1){
System.out.print(a[a.length-1]+"]");
}else{System.out.print(a[i]+",");}
}
}
public static void random_quicksort(int[] a){
if(a==null||a.length<2){ //判断数组是否为空以及数组元素个数
return;
}
random_quicksort(a,0,a.length-1);
}
public static void random_quicksort(int[] a,int p,int r){
if(p<r){
int q=random_partition(a,p,r);
random_quicksort(a, p, q-1);
random_quicksort(a, q+1, r);
}
}
public static void swap(int i,int j,int[] a){
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
public static int random_partition(int[] a,int p,int r){
int i=(int)(Math.random()*(r-p+1)); //返回p到r之间的随机整数
swap(p+i,r,a); //数组中随机一个数与最后一个数进行交换,作为数组划分的依据
return PARTITION(a,p,r);
}
public static int PARTITION(int[] a,int p,int r){
int x=a[r];
int i=p-1;
for(int j=p;j<r;j++){
if(a[j]<=x){
i=i+1;
int temp=a[i];
a[i]=a[j];
a[j]=temp;
}
}
int temp=a[i+1];
a[i+1]=a[r];
a[r]=temp;
return i+1;
}
}
测试: