关于快速排序的认识及其基本特征,请参寻我的另外一篇博文快速排序——以数组第一个元素为主元
以具体的例子,参照July大神博客里给的思路,飞起来——July大神的快排思路
大神博客里给的是伪代码的形式来表述思路,这里我用大白话来解释一下,让大家看起来更放松一些
首先,我们能够确定排序所用的参照系,也就是数组最后一个元素(这里我用temp来表示,程序中同样如此),比较特别的是,我们定义的i和j两个参数都是从要排序的数组段的第一个元素位置开始,以此往右移动。
我们暂且定义i是用来寻找大于temp的参数,j是用来找到小于等于temp的。
从图示中可以看出j总是在i的右侧,因为在左侧的话就没必要执行交换操作了,我们是要把小数交换到左侧,留下大数不动来实现的
下面看程序
package 快速排序2;
public class QuickSort2 {
static int[] arr={2,8,7,1,3,5,6,4};
static int num=1;
public static void main(String[] args) {
quickSort(arr,0,7);
}
public static void quickSort(int[] arr,int low,int high){
int temp=arr[high];
int i=low,j=low;
int ex;
while(j<high){
//找到大于temp的数
while(arr[i]<=temp){
i++;
/*
* 实在找不到,说明low到high之间没有比arr[high]更大的数了
* 也就是说此时temp左边的数都小于等于temp
* 此时,本次排序结束,退出循环
*/
if(i>=high)break;
}
//找到小于或等于temp的数
while(arr[j]>temp||j<i){
j++;
/*
* 为了防止i=high时,根据j<i就可以继续循环这一条件,j可能大于high,导致溢出
* 所以必须加上下面的条件
*/
if(j>=high)break;
}
//执行数值交换
ex=arr[i];
arr[i]=arr[j];
arr[j]=ex;
}
System.out.println("第"+num+++"次排序,i在"+arr[i]+"的位置,可见"+arr[i]+"左边的数(有数的情况下)全部小于"+arr[i]+",而其右边的数(有数的情况下)全部大于"+arr[i]);
//遍历输出
each(arr);
//根据条件执行递归调用
if(i-1>low){
quickSort(arr,low,i-1);
}
if(i+1<high){
quickSort(arr, i+1, high);
}
}
public static void each(int[] arr){
for(int i=0;i<arr.length;i++){
System.out.print(arr[i]+" ");
}
System.out.println();
}
}
大神给出了时间复杂度和空间复杂度的推导方法,不说话,直接拿过来
快速排序的最坏情况和最快情况。
最坏情况发生在划分过程产生的俩个区域分别包含n-1个元素和一个0元素的时候,即假设算法每一次递归调用过程中都出现了,这种划分不对称。那么划分的代价为O(n),
因为对一个大小为0的数组递归调用后,返回T(0)=O(1)。
估算法的运行时间可以递归的表示为:
T(n)=T(n-1)+T(0)+O(n)=T(n-1)+O(n).
可以证明为T(n)=O(n^2)。
因此,如果在算法的每一层递归上,划分都是最大程度不对称的,那么算法的运行时间就是O(n^2)。
亦即,快速排序算法的最坏情况并不比插入排序的更好。
此外,当数组完全排好序之后,快速排序的运行时间为O(n^2)。
而在同样情况下,插入排序的运行时间为O(n)。
//注,请注意理解这句话。我们说一个排序的时间复杂度,是仅仅针对一个元素的。
//意思是,把一个元素进行插入排序,即把它插入到有序的序列里,花的时间为n。
再来证明,最快情况下,即PARTITION可能做的最平衡的划分中,得到的每个子问题都不能大于n/2.
因为其中一个子问题的大小为|_n/2_|。另一个子问题的大小为|-n/2-|-1.
在这种情况下,快速排序的速度要快得多。为,
T(n)<=2T(n/2)+O(n).可以证得,T(n)=O(nlgn)。
直观上,看,快速排序就是一颗递归数,其中,PARTITION总是产生9:1的划分,
总的运行时间为O(nlgn)。各结点中示出了子问题的规模。每一层的代价在右边显示。
每一层包含一个常数c。