挑选一个元素作为标点,把数组划分为小于和大于的区域;
需注意标点的选取(如果取第0个,在有序情况下会退化,栈溢出);
相等元素处理(如果全部相等,算法退化,栈溢出,二路快排,三路快排)
关键点:看partition函数写法,起始点,循环不变量及结束点
快排
partition函数维护小于arr[l]部分,起始点在l(可能都大于arr[l]),结束部分做交换后,返回即为等于部分。
import java.util.Random;
public class QuickSort {
public static > void sort(E[] arr) {
sort(arr,0,arr.length-1,new Random()); // 闭区间
}
public static > void sort(E[] arr, int L, int r, Random random) {
if (L>=r) {
return;
}
int jIndex = partition(arr,L,r,random);
sort(arr,L,jIndex-1,random);
sort(arr,jIndex+1,r,random);
}
public static > int partition(E[] arr, int L, int r ,Random random) {
/**
* 有序的数组,
* 分割左右部分时,只能区分首节点和大于的部分,会一直递归下去,导致栈溢出,
* 时间复杂度为O(n²),空间复杂度使用堆栈n次调研堆栈
*
* 使用随机值解决index值解决该问题
* (虽然解决了有序数组问题,但遇到全部相等的数组又会退化)
*/
int p = L + random.nextInt(r-L+1);
swap(arr,p,L);
// 循环不变量 arr[L+1...j]v
int j = L; // 注意j的初始化位置
for (int i = L+1; i <= r; i++) {
//arr[i]小于arr[L],j++
//大于j的位置不变,不操作
if (arr[i].compareTo(arr[L])<0) {
j++;
swap(arr,i,j); // j 处的位置小于arr[L]
}
}
swap(arr,L,j);
return j;
}
private static void swap(E[] arr,int i,int j){
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
2路快排:解决元素全部相等的算法退化
循环不变量(双路实现,解决全部相等问题,等于部分平均分在两边,位置定位在中间)
注意partition函数
1、初始化: int i = L+1 , j=r ,
2、循环体中,先循环找出左边小于arr[l]和右边大于arr[l],循环结束的地方即为左边大右边小,需要交换,注意交换后对值进行了++和--,导致最后右边指针指向小于l处的值
3、结束时,j指向元素<=arr[l],左后和j交换
import java.util.Random;
/**
* 双路快速排序(主要加入等于的判断,平均分布在小于和大于区间)
*/
public class QuickSort2Ways {
public static > void sort(E[] arr) {
sort(arr,0,arr.length-1,new Random()); // 闭区间
}
public static > void sort(E[] arr, int L, int r, Random random) {
if (L>=r) {
return;
}
int jIndex = partition(arr,L,r,random);
sort(arr,L,jIndex-1,random);
sort(arr,jIndex+1,r,random);
}
public static > int partition(E[] arr, int L, int r ,Random random) {
/**
* 有序的数组,
* 分割左右部分时,只能区分首节点和大于的部分,会一直递归下去,导致栈溢出,
* 时间复杂度为O(n²),空间复杂度使用堆栈n次调研堆栈
*
* 使用随机值解决index值解决该问题
* (虽然解决了有序数组问题,但遇到全部相等的数组又会退化)
*/
int p = L + random.nextInt(r-L+1);
swap(arr,p,L);
// 循环不变量(双路实现,解决全部相等问题,位置定位在中间) arr[L+1...j]<=v ; arr[j+1..i]>=v
int i = L+1 , j=r; // 注意j的初始化位置
while (true) {
while (i<=j && arr[i].compareTo(arr[L])<0) { // arr[L+1...j]<=v 如果遇到大于arr[L],终止循环
i++;
}
while (j>=i && arr[j].compareTo(arr[L])>0) { //arr[j+1..i]>=v 如果遇到小于arr[L],终止循环
j--;
}
if (i>=j) { // i>j 超过大于和小于范围, i==j 证明该处等于arr[L]
break;
}
// i处为大于arr[L],j为小于arr[L],交换位置,符合区间范围
swap(arr,i,j);
i++;
j--;
}
swap(arr,L,j);
return j;
}
private static void swap(E[] arr,int i,int j){
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
3路快排
把区域划分为小于、等于、大于三部分
partition函数:
1、初始位置int lt = L, i= L+1 , gt=r+1,lt和gt在范围之外(全部大于或小于),
2、过程中[l+1...lt]
3、结尾lt和l交换后,[lt...gt)==arr[l]
import java.util.Random;
/**
* 三路快速排序(partition中将数组分成三段,小于,等于,大于)
*/
public class QuickSort3Ways {
public static > void sort(E[] arr) {
sort(arr,0,arr.length-1,new Random()); // 闭区间
}
public static > void sort(E[] arr, int L, int r, Random random) {
if (L>=r) {
return;
}
int[] jIndex = partition(arr,L,r,random);
sort(arr,L,jIndex[0]-1,random);
sort(arr,jIndex[1],r,random);
}
public static > int[] partition(E[] arr, int L, int r ,Random random) {
/**
* 有序的数组,
* 分割左右部分时,只能区分首节点和大于的部分,会一直递归下去,导致栈溢出,
* 时间复杂度为O(n²),空间复杂度使用堆栈n次调研堆栈
*
* 使用随机值解决index值解决该问题
* (虽然解决了有序数组问题,但遇到全部相等的数组又会退化)
*/
int p = L + random.nextInt(r-L+1);
swap(arr,p,L);
// 循环不变量(三路实现,解决全部相等问题,区间分成三段)
// arr[L+1...lt-1]v
int lt = L, i= L+1 , gt=r+1; // 初始区间为空,lt和gt都没有指向区域第一个(参考循环不变量范围)
while (i 0) {
gt--;
swap(arr,i,gt); // 交换位置后,i处新值还未做比较,所以不i++
} else { // 相等arr[L],不做处理,因为lt+1到i-1区间都等于arr[L]
i++;
}
}
swap(arr,L,lt);
return new int[]{lt,gt};
}
private static void swap(E[] arr,int i,int j){
E t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}