交换排序之快速排序
一、基本思想
快速排序(quick sort)是由冒泡排序改进而得的,它的基本思想是在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放入适当位置后,数据序列被此元素划分成两部分。所有关键字比该元素关键字小的元素放置在前一部分,所有比它大的元素放置在后一部分,并把该元素排在这两部分的中间(称为该元素归位),这个过程称为一趟快速排序,即一趟划分。
之后对产生的两个部分分别重复上述过程,直至每部分内只有一个元素或空为止。简而言之,每趟使表的第一个元素放入适当位置,将表一分为二,对子表按递归方式继续这种划分,直至划分的子表的长为1或0。
二、Java代码实现
一趟排序实现
/**
* 快速排序的一趟排序过程
* @param arr
*/
private static int onceSort(Integer[] arr, int s, int t){
int i = s;
int j = t;
int temp = arr[i];
// 从两端交替向中间扫描,直到 i = j 为止
while (i < j){
// j从后往前找到一个比基准temp小的数
while (j > i && temp < arr[j])
j --;
// 把找到的数存放在i的位置
arr[i] = arr[j];
// i从前往后找比基准temp大的数
while (i < j && temp >= arr[i])
i ++;
// 把找到的大数替换掉之前找到的小数
arr[j] = arr[i];
}
// 最后把基准归位
arr[i] = temp;
// 返回一趟排序基准归为的位置i
// 此时i之前的数都比arr[i]小,i之后的数都比arr[i]大
return i;
}
递归实现快排
/**
* 快速排序,通过递归实现
* @param arr 待排序的集合
* @param s 排序区间的起始索引
* @param t 排序区间的终止索引
*/
public static void quickSort(Integer[] arr, int s, int t){
int i;
if (s < t){
i = onceSort(arr,s,t);
quickSort(arr,s,i-1);
quickSort(arr,i+1,t);
}
}
main方法测试及结果
public static void main(String[] args) {
Integer [] a = {1, 5, 8, 4, 3, 7, 10, 6};
System.out.println(a);
System.out.println("==================");
System.out.println(Arrays.toString(a));
// 快速排序
// 这里排序区间的终止索引不能为a.length,会发生数组索引越界异常
quickSort(a,0,a.length-1);
System.out.println(Arrays.toString(a));
}
排序结果
三、算法分析
快速排序最好的情况:每一次划分都将n个元素划分为两个长度差不多相同的子区间。
也就是说﹐每次划分所取的基准都是当前无序区的“中值”元素,划分的结果是基准的左、右两个无序子区间的长度大致相等,这样的递归树高度为O(),而每一层划分的时间为O(n),所以此时算法的时间复杂度为O()、空间复杂度为O()。
快速排序最坏的情况:每次划分选取的基准都是当前无序区中关键字最小(或最大)的元素,划分的结果是基准左边的子区间为空(或右边的子区间为空),而划分所得的另一个非空的子区间中元素的数目仅比划分前的无序区中的元素个数减少一个。
这样的递归树高度为n,需要做n―1次划分,此时算法的时间复杂度为、空间复杂度为O(n)。
在平均情况下,每一次划分将n个元素划分为两个长度分别为k一1和n一k的子区间,k的取值范围是1~n,共n种情况。设执行时间为,显然有:
由上式可以推导出,上式cn表示划分的时间。
结论:
由以上分析可知,快排最好的情况下时间复杂度为O()、空间复杂度为O();最坏的时间复杂度为、空间复杂度为O(n)。平均时间复杂度为。也就是说算法的平均时间复杂度接近最好的情况。
当初始数据序列为正序或反序时,呈现出最坏的情况。如果初始数据序列是随机的,每次可以划分为两个长度差不多的子区间,会呈现最好的情况。