快速排序算法思想:
假设数组arr中有N个数,则数组下标的范围是 0 ~ N - 1:
(1)在数组下标范围内生成一个随机数index,交换arr[index]和arr[N - 1];
(2)利用arr[N - 1]做划分值,将 0 ~ N - 2上的数组元素划分成:左边是小于arr[N - 1]的区域,中间是等于arr[N - 1]的区域,右边是大于arr[N - 1]的区域
(3)然后交换大于区域最左侧的元素和arr[N - 1],至此,数组arr在 0 ~ N - 1 范围上完全划分成:左边是小于arr[N - 1]的区域,中间是等于arr[N - 1]的区域,右边是大于arr[N - 1]的区域。
(4)等于arr[N - 1] 的区域数据位置已经确定,接下来在小于区域和大于区域上重复步骤(1)、(2)和(3)。
快速排序代码:
public class QuickSort {
public void quickSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
quickSort(arr, 0, arr.length - 1);
}
public void quickSort(int[] arr, int l, int r) {
if (l < r) {
int index = l + (int) (Math.random() * (r - l + 1));
swap(arr, index, r);
int[] p = partition(arr, l, r);
// p[0]表示的是小于区域 的右边界,但是数组可能没有小于区域,也就是p[0]的值可能是-1,
// 此时l = 0的话,l > p[0],要排序的数组区域左边界大于右边界,此时是不能进行排序的
// 所以在进行排序之前,要先判断 l < r 是否成立,若是的话,说明待排序区域有两个元素,
// 此时再进行排序
quickSort(arr, l, p[0]);
quickSort(arr, p[1], r);
}
}
public int[] partition(int[] arr, int l, int r) {
int less = l - 1;
int more = r;
while (l < more) {
if (arr[l] < arr[r]) {
swap(arr, ++less, l++);
} else if (arr[l] == arr[r]) {
l++;
} else {
swap(arr, --more, l);//此时l指针原地不动,对交换过来的新元素再进行一次比较
}
}
swap(arr, more, r);
return new int[] { less, ++more };
}
public void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
快速排序的时间复杂度解析:
快速排序算法虽然有递归行为,但是子问题并不是等规模的,所以不能用master公式来求快速排序的时间复杂度。
快速排序算法中,数组的每一个元素被选择成为划分值的概率是一样的,这样时间复杂度比较低的情况和时间复杂度比较高的情况变成了概率时间。快速排序就是所有情况下的时间复杂度与其概率值做运算,累加求平均值得到的,为O(N * log(N))。
快速排序的额外空间复杂度解析:
快速排序的额外空间复杂度是由递归过程中,p数组的压栈造成的。也是所有情况下的额外空间复杂度与其概率值做运算,累加求平均值得到的,为O(logN)。
小拓展:
(1)最坏情况下,快排的额外空间复杂度是O(N):假设每次选到的数都是要排序数组中的最大值,此时递归有N层。
(2)最好情况下,快排的额外空间复杂度是O(logN):假设每次选到的数都是要排序数组的中间值,这样数组就以完全二叉树形式展开,最后一层展开成一个一个单独的数,排序完成,这些单独的数有N个,当完全二叉树高度为log(N) + 1时候,最后一层才能容纳下N个一个一个的单独的数,则递归开的最大的层数是log(N),所以,此时快排的额外空间复杂度是O(log(N))。