快速排序法的原理
每次从当前考虑的数组中选择一个元素,以这个元素为基点,之后将这个元素挪到排好序的时候应该所在的位置。
这样就有一个性质,这个元素之前的所有元素小于它,之后的所有元素大于它。然后分别对这两边进行快速排序,逐渐递归下去,完成整个排序过程。
Partition
更加抽象的说,对于数组的一个区间,如果我们选定了一个标定点v,如何把v放到正确的位置,使得v前面的元素都是小于它,v后面的元素都是大于它。那么这样的一个过程被称为partition。(笔者画图如下)
如果允许开辟额外的空间,是非常容易解决这个问题的。只需要开辟两个空间left和right,left存储v(4)左侧的值,rigth存储v(4)右侧的值,然后遍历一遍数组中所有的值(标定点不需要遍历)。
笔者在这里画了个图如下:
最后整理下数组,就排成如下这样了。(图可能画的有点歪,不要在乎这些细节)
这样一来我们已经完成了partition要做的事情,但这种使用了额外空间,非原地排序。
但对于快速排序来说,之所以快是不需要申请额外空间的内存操作。那么如何原地进行partition?
下面笔者介绍下原地完成partition的思路:
假设对如下数组区间进行一个partition过程
依然使用数组区间中第一个元素v(l位置)作为标定点,之后逐渐遍历右边所有没被访问过的元素,在遍历的过程中,我们将逐渐整理让数组一部分是小于v,另外一部分是大于v的元素,用 j 索引记录这个分界点,当前访问的元素用 i 来标记。
如果当前访问的元素e大于v则:
如果当前访问的元素e小于v则:
对整个数组进行一次遍历后整个数组就划分成三部分了:
最后将 l 位置和 j 位置进行一次交换如下
第一版快速排序法
import java.util.Arrays;
/**
* 描述 快速排序法
*
* @author lixinzhen
* @create 2021/9/30 10:10
*/
public class QuickSort {
private QuickSort() {
}
public static <E extends Comparable<E>> void sort(E[] arr) {
sort(arr, 0, arr.length - 1);
}
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) {
if (l >= r) return;
int p = partition(arr, l, r);
sort(arr, l, p - 1);
sort(arr, p + 1, r);
}
private static <E extends Comparable<E>> int partition(E[] arr, int l, int r) {
//arr[l+1..j] < v ; arr[j+1..i] > v
int j = l;//标定点所在位置
for (int i = l + 1; i <= r; i++)
if (arr[i].compareTo(arr[l]) < 0) {
j++;
Swap(arr, i, j);
}
Swap(arr, l, j);
return j;//返回标定点所在位置的索引
}
private static <E extends Comparable<E>> void Swap(E[] arr, int i, int j) {
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
public static void main(String[] args) {
Integer[] arr = {4, 6, 2, 3, 1, 5, 7, 8};
sort(arr);
System.out.println(Arrays.toString(arr));
}
}
第一版快速排序时间复杂度是O(n),如果是一个完全有序的数组,则时间复杂度为O(n^2)。