快排算法
快排就是快速排序,是通过不断比较和移动交换来进行排序,相当于冒泡排序的一种升级。
其基本思想是:
分而治之,也就是把一组数组分成两个独立数组,再对两个独立的数组再次进行排序。
其步骤为:
第一步确定分界点,一般分界点可取左边界、中间值、右边界或者随机值。
第二步调整区间,使得所以小于分界点的值放在左边,所以大于分界点的值放在右边。
第三步进行递归处理,先把左边进行排序,再把右边排序。
package com.jinyu.day01;
import java.util.Arrays;
public class Code04_QuickSort {
public static void quickSort(int[] arr, int left, int right) {
if (left < right) {
//随机下标的元素跟最后元素互换
swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
int[] p = partition(arr, left, right);
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
}
//这是一个处理arr[l...r]的函数
//默认以arr[r]这个数进行划分,arr==p[r] <p ==p >p
//返回的是等于区域的左右边界
public static int[] partition(int[] arr, int left, int right) {
int less = left - 1; //<区右边界
int more = right;// >区左边界
while (left < more) { //l表示当前数的位置 arr[r]表示的是划分值
//当前数小于划分数 则当前数与 <区 的右边界的第一个数交换然后 <区 的右边界右移一位 left也要加1
if (arr[left] < arr[right]) {
swap(arr, left++, ++less);//理解left++, ++less的含义
} else if (arr[left] > arr[right]) {//当前数大于划分数时 当前数与 >区 前一个数交换, >区 左边界左移一位
swap(arr, --more, left);
} else { //如果当前数与划分数相等,则只需要当前数往前走一位就行
left++;
}
}
swap(arr, more, right);//处理划分数
return new int[]{less + 1, more};
}
public static void swap(int[] arr, int a, int b) {
int temp = 0;
temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
public static void main(String[] args) {
int[] arr = {2, 1, 5, 4, 9, 6};
quickSort(arr, 0, arr.length - 1);
System.out.println(Arrays.toString(arr));
}
}
快排最需要注意的就是
partition(int[] arr, int left, int right)
,他是快排算法的核心,它的作用就是将数组按照分界点划分成左右两个子序列,左序列所有数据都小于分节点,右序列所有序列都大于分界点,这里还需要注意,需要排序的数组里面存在可能存在着重复元素,因此partition
的返回值是一个数组,其含义是左序列的右边界和右序列的左边界。
partition
这个思想还会在topK问题中运用到。
性能分析
稳定性
由于在快速排序中,元素的比较和交换是跳跃进行的,所以快速排序是一种不稳定的排序算法。
复杂度分析
快速排序的平均时间复杂度是O(nlogn),但是在实际排序中,时间复杂度和基准元素(枢轴)的选择有关。如果枢轴选取不好,那么快速排序有可能就会退化为冒泡排序,时间复杂度为O(n*n)。
由于快速排序是通过递归实现的,而递归又要依靠栈空间来实现,所以快速排序相对于其它排序更耗费空间资源。
通常来说,为了避免快速排序退化为冒泡排序,以及递归栈过深的问题,我们一般依据“三者取中”的法则来选取基准元素,“三者”即序列首元素、序列尾元素、序列中间元素,在三者中取中值作为本趟快速排序的基准元素