原理-总览
1、从待排序区间选择一个数,作为基准值privot;
2、Partition:遍历整个待排序区间,将比基准值小的(可以包含相等的)放到基准值的左边,将比基准值大的(可以包含相等的)放到基准值的右边
3、采用分治思想,对左右两个校区间按照同样的方式处理,直到小区间的长度==1,代表已经有序,或者小区间的长度==0,代表没有数据。
---------------------------------------------------------------------------------------------------------------------------------搞明白原理之后,咱们就可以逐步解决,基准值我们最先能想到的一般都是首元素或者尾元素
注意:如果我们每次唯一的选择首元素或者尾元素,但是如果这个数组是一个近乎有序的数组,快排的时间复杂度其实无异于O(n^2),随意我们通过随机选数来解决这一问题。
代码:
/**
* 在arr[l..r]上进行快速排序
* @param arr
* @param l
* @param r
*/
private static void quickSortInternal(int[] arr, int l, int r) {
// 先获取分区点
// 所谓的分区点就是经过分区函数后,某个元素落在了最终的位置
// 分区点左侧全都是小于该元素的区间,分区点右侧全都是 >= 该元素的区间
int p = partition(arr,l,r);
// 重复在左区间和右区间上重复上述流程
quickSortInternal(arr,l,p - 1);
quickSortInternal(arr,p + 1,r);
}
/**
* 在arr[l..r]上的分区函数,返回分区点的索引
* @param arr
* @param l
* @param r
* @return
*/
private static int partition(int[] arr, int l, int r) {
// 随机在当前数组中选一个数
int randomIndex = random.nextInt(l,r);
swap(arr,l,randomIndex);
int v = arr[l];
// arr[l + 1..j] < v
// arr[j + 1..i) >= v
// i表示当前正在扫描的元素
int j = l;
for (int i = l + 1; i <= r; i++) {
if (arr[i] < v) {
swap(arr,j + 1,i);
j ++;
}
}
// 将基准值和最后一个 < v的元素交换,基准值就落在了最终位置
swap(arr,l,j);
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
还有一种情况:如果所有元素都是相同的呢?你随机选择的话时间复杂度也变成了O(n^2),成了一个极度不平衡的树,只能通过遍历解决。
既然问题在于重复元素过多,那我们就在多设置一个区间存储相等的元素。
代码:
private static void quickSortInternal2(int[] arr, int l, int r) {
int p = partition2(arr,l,r);
quickSortInternal2(arr,l,p - 1);
quickSortInternal2(arr,p + 1,r);
}
/**
* 二路快排的分区
* 在arr[l..r]上进行分区处理
* @param arr
* @param l
* @param r
* @return
*/
private static int partition2(int[] arr, int l, int r) {
int randomIndex = random.nextInt(l,r);
swap(arr,l,randomIndex);
int v = arr[l];
// arr[l + 1..i) <= v
// [l + 1..l + 1) = 0
int i = l + 1;
// arr(j..r] >= v
// (r...r] = 0
int j = r;
while (true) {
// i从前向后扫描,碰到第一个 >= v的元素停止
while (i <= j && arr[i] < v) {
i ++;
}
// j从后向前扫描,碰到第一个 <= v的元素停止
while (i <= j && arr[j] > v) {
j --;
}
if (i >= j) {
break;
}
swap(arr,i,j);
i ++;
j --;
}
// j落在最后一个 <= v的元素身上
swap(arr,l,j);
return j;
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
最后给介绍一下三路快排
代码:
private static void quickSortInternal3(int[] arr, int l, int r) {
int randomIndex = random.nextInt(l,r);
swap(arr,l,randomIndex);
int v = arr[l];
// 这些变量的取值,一定是满足区间的定义,最开始的时候,所有区间都是空
// arr[l + 1..lt] < v
// lt是指向最后一个<v的元素
int lt = l;
// arr[lt + 1..i) == v
// i - 1是最后一个 = v的元素
int i = lt + 1;
// arr[gt..r] > v
// gt是第一个 > v的元素
int gt = r + 1;
// i从前向后扫描和gt重合时,所有元素就处理完毕
while (i < gt) {
if (arr[i] < v) {
// arr[l + 1..lt] < v
// arr[lt + 1..i) == v
swap(arr,i,lt + 1);
i ++;
lt ++;
}else if (arr[i] > v) {
// 交换到gt - 1
swap(arr,i,gt - 1);
gt --;
// 此处i不++,交换来的gt - 1还没有处理
}else {
// 此时arr[i] = v
i ++;
}
}
// lt落在最后一个 < v的索引处
swap(arr,l,lt);
// arr[l..lt - 1] < v
quickSortInternal3(arr,l,lt - 1);
// arr[gt..r] > v
quickSortInternal3(arr,gt,r);
}
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}