快速排序概述
快速排序是我们最常用的一种排序方法之一,它使用了分治思想。快速排序是对冒泡排序的一种改进。
原理
- 通过一趟排序,将原数据分割为独立的2个部分,其中一部分的所有数据均比另一部分的所有数据小。
- 然后,再按照此方法对这两部分的数据进行快速排序,整个排序过程可以递归进行。
- 最终,整个数据编程有序序列。
复杂度
时间复杂度:最好情况是O(nlogn),最差情况是O(n²),它的平均时间复杂度为O(nlogn)。
快速排序的平均性能非常好,通常是实际排序应用中最好的选择。
空间复杂度:快速排序是一种原址排序,只需要一个很小的栈作为辅助空间,它的空间复杂度为O(logn),所以适合在数据集比较大的时候使用。
步骤
快速排序的三步分治过程:
- 分解:数组A[s…e]被划分为2个子数组A[s…q-1]和A[q+1…e],使得A[s…q-1]中的每个元素都小于等于A[q],而A[q]也小于等于A[q+1…e]中的每个元素。其中计算下标q也是划分过程的一部分。
- 解决:通过递归调用对子数组进行排序。
- 合并:因为子数组都是原址排序,所以不需要合并操作。
快速排序的实现
这里介绍2个方案,我们来看。
方案一
public static void quickSort(int[] arr, int s, int e){
if(s<e){
int q = partition(arr, s, e);
quickSort(arr, s, q-1);
quickSort(arr, q+1, e);
}
}
private static int partition(int[] arr, int s, int e){
int base = arr[e];//选出最后一个作为主元(对比基数)
int i = s - 1;//小于主元值的数组边界。开始时为-1。
int t;
for(int j=s;j<e-1;j++){//找出小于主元的值
if(arr[j] <= base){
i++;//小于主元的数组边界i增加一个
if(i != j){//防止它们相等时执行交换,原数组序列正确时会出现
//这时,j位置的元素小于base值,i位置的元素是大于主元值的(想一想这是为什么?),这里执行交换
t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
}
//最后需要把小于等于主元的值边界再次扩张1,这时A[i+1]是大于主元的,所以把主元A[r]和它进行交换,本次排序结束。
t = aar[i+1];
aar[i+1] = aar[e];
aar[e] = t;
return i+1;//返回主元的当前位置
}
方案一的具体实现逻辑请看注释,这里简单归纳一下:
- quickSort方法负责快速排序过程,参数为数组arr,数组起始元素位置s,数组结尾元素位置e。
- 当s<e时。
- 以数组尾部元素为主元,找出主元在一轮排序完成后的正确位置,这里执行方法partition来完成。
- 最后递归调用,对主元素左侧和右侧执行快速排序过程。
- partition中,以数组尾部元素为主元,i是小于主元的数组边界,通过一个for循环遍历,查找小于主元的值,依次添加到当前i+1的位置,并且扩展小于主元的数组边界i。循环结束后,将主元位置放在正确的地方,然后返回当前位置即可。
方案二
public static void quickSort(int[] arr, int s, int e){
int i,j;//i为小于主元边界,j为大于主元的边界
int base = arr[s];//主元(对比的基值)
int t;//临时变量
if(s>e){
return;
}
i = s;
j = e;
while(i<j){
//先看右边,依次往左递减
while(base <= arr[j]&&i<j){
j--;
}
//再看左边,依次往右递增
while(base >= arr[i]&&i<j){
i++;
}
//如果满足条件则交换
if(i<j){
t = arr[j];
arr[j] = arr[i];
arr[i] = t;
}
}
//最后将基准为与i和j相等位置的数字交换
arr[s] = arr[i];
arr[i] = base;
quickSort(arr, s, j-1);
//递归调用右半数组
quickSort(arr, j+1, e);
}
方案二中,思想和方案一是类似的,但是我们使用了2个标记为指针来指示小于主元边界和大于主元边界,最后执行递归完成排序过程。
总结
- 快速排序是我们最常用的一种排序方法之一,它使用了分治思想。快速排序是对冒泡排序的一种改进。
- 通过一趟排序,将原数据分割为独立的2个部分,其中一部分的所有数据均比另一部分的所有数据小。然后,再按照此方法对这两部分的数据进行快速排序,整个排序过程可以递归进行。最终,整个数据编程有序序列。
- 快速排序的时间复杂度:最好情况是O(nlogn),最差情况是O(n²),它的平均时间复杂度为O(nlogn)。
- 快速排序是一种原址排序,只需要一个很小的栈作为辅助空间,它的空间复杂度为O(logn),所以适合在数据集比较大的时候使用。
- 快速排序的平均性能非常好,通常是实际排序应用中最好的选择。
- 最后我们在实战中,使用了2种方式,实现了快速排序的过程。