快速排序
在了解快速排序前,我们先通过了解一个问题:如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T6WxhrgA-1686740233028)(E:\zy-o\Vue\image-20221122163432880.png)]
荷兰国旗问题一
我们首先来实现荷兰国旗的问题一:
原数组数据为:3 5 6 7 4 3 5 8
使用图解,我们来解决荷兰国旗问题:如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YRyfT0BO-1686740233030)(E:\zy-o\Vue\image-20221122165947304.png)]
核心算法:
// partition 分层
// arr[L...R]部分进行分层操作,也就是划分操作
public static void partition(int[] arr,int L,int R){
// 假定按照整个数据的最后一个数据值以依据来划分,记作p 则 p = arr[R];
// 定义<=p 区域的边界
int less = L - 1; //小于区域的边界一开始是整个数据的左侧,因此是L - 1;
// 开始操作:
// 因此具体实现算法分为了两个过程:
// 1) 如果arr[i] <= 5,我们要将它放到红色区域中,也就是<=5的区域中
// 具体操作为: 使arr[i] 与 红色区域后的下一个元素进行互换,然后红色区域右扩1,i++;
// 2) 如果arr[i] > 5,那么无需移动,i++
// L所在的左边,就是指针开始位置,也就是上述i的含义
while (L < R){ //如果L 一直向右移动,直到整个数据的右边界后就表名已经排好序了.
if (arr[L] <= 5){
// 交换arr[L] 和 红色区域的下一个元素,然后L++
// 红色区域右扩
swap(arr,L++,++less);
// System.out.println(Arrays.toString(arr));
}
if(arr[L] > 5){
// 原地不动,L++
L++;
// System.out.println(Arrays.toString(arr));
}
}
// 至此,partition算法完成
}
swap算法
// 交换数组中的两个数
public static void swap(int []arr, int i,int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
荷兰国旗问题二
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jctOJBAC-1686740233030)(E:\zy-o\Vue\image-20221122172034155.png)]
我们首先来实现荷兰国旗的问题二:
原数组数据为:3 5 6 7 4 3 5 8
使用图解,我们来解决荷兰国旗问题二:如下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a1KKIejN-1686740233030)(E:\zy-o\Vue\image-20221122183518211.png)]
核心算法:
// partition 分层
// arr[L...R]部分进行分层操作,也就是划分操作
public static void partition(int[] arr,int L,int R){
// 假定按照整个数据的最后一个数据值以依据来划分,记作p 则 p = arr[R];
// 定义<=p 区域的边界
int less = L - 1; //小于区域的边界一开始是整个数据的左侧,因此是L - 1;
int more = R + 1; //大于区域的边界一开始是整个数据的右侧,因此是R + 1。
int p = arr[R]; //因为在变化过程中,arr[R]是会发生变化的,因此需要先记录下来
// 开始操作:
// 因此具体实现算法分为了三个过程:
// 1) 如果arr[i] < 5,我们要将它放到红色区域中,也就是<5的区域中
// 具体操作为: 使arr[i] 与 红色区域后的下一个元素进行互换,然后红色区域右扩1,i++;
// 2) 如果arr[i] > 5,我们要将它放到蓝色区域中,也就是>5的区域中
// 具体操作为: 使arr[i] 与 蓝色区域前的前一个元素进行互换,然后蓝色区域左扩1,i不变;
// 因为 互换过来的数据我们不知道其大小,因此i不能变,应该继续判断
// 3)如果arr[i] == 5,无需移动,直接让i++即可
// 实质就是 < 区域推动着==区域,当与>区域撞上时,就表示分配完成了
// 那就是L 与 more撞上的时候,就表示分配完成了
// L所在的左边,就是指针开始位置,也就是上述i的含义
while (L < more){ //如果L 一直向右移动,直到整个数据的右边界后就表名已经排好序了.
if (arr[L] < p){
// 交换arr[L] 和 红色区域的下一个元素,然后L++
// 红色区域右扩
swap(arr,L++,++less);
// System.out.println(Arrays.toString(arr));
}else if(arr[L] > p) {
swap(arr, L, --more);
}else {
// 如果相等原地不动,指针后移
L++;
}
}
// 至此,partition算法完成
}
swap算法:
// 交换数组中的两个数
public static void swap(int []arr, int i,int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
完成了上述荷兰问题的解析后,接下来我们就可以引出快速排序了。
快排v1.0
首先先来说一下快排v1.0版本,基于荷兰问题的想法,再加上排序,怎么排序呢?
快排v1.0是基于荷兰国旗问题一得思想来排序的。
具体过程如下:使用图解的形式给出
原数组数据为:3 5 6 7 4 3 5 5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KnTLWyaf-1686740233031)(E:\zy-o\Vue\image-20221122210710344.png)]
快排v2.0
再来说一下快排v2.0版本,基于荷兰问题的想法,再加上排序,怎么排序呢?
快排v2.0是基于荷兰国旗问题一得思想来排序的。
具体过程如下:使用图解的形式给出
原数组数据为:3 5 6 7 4 3 5 5
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28TnnbXg-1686740233031)(E:\zy-o\Vue\image-20221122213316682.png)]
快排v3.0
上面讲述了快排v1.0和快排v2.0
但是这两种快速排序的时间复杂度为O(n^2) ,因为这两种快速排序每次都是选取最后一个位置的元素作为基准,来进行partition(分层)
不可避免的会遇上最坏情况的数据。
于是快排v3.0就来改进这种情况,快排v3.0主要解决的是不总是选取最后一个位置的元素的值作为基准。来进行partition
具体操作为: 随机选取数据中的一个元素,使其与最后一个位置的元素进行交换.
然后再以新的最后一个位置的元素为基准,来进行partition
具体图解如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zqN1U7IT-1686740233031)(E:\zy-o\Vue\image-20221122214156503.png)]
核心算法:
public static void quicksort(int[] arr,int L,int R){
if (arr == null || arr.length < 2) return;
process(arr,0,arr.length - 1);
}
public static void process(int[] arr,int L,int R){
if (L < R){
//随机获取元素
int p = L + (int)(Math.random() * (R - L + 1));
// 交换元素
swap(arr,p,R);
//进行partition
int []pos = partition(arr,L,R);
System.out.println(Arrays.toString(pos));
// 获取左右边界的位置后,再将其根据左右边界,划分成两个独立的部分,再次进行递归排序
// 不包括左右边界,因此pos[0] - 1 和 pos[1] + 1
process(arr,L,pos[0] - 1);
process(arr,pos[1] + 1,R);
}
}
public static int[] partition(int []arr, int L,int R){
// 记录小于区域左边界
int less = L - 1;
// 记录大于区域右边界
//因为有哨兵占用了位置,我们不对哨兵进行操作,因此右边界为R,若没有哨兵,那么应为R + 1
int more = R;
// L 表示指向当前元素的指针
while (L < more){
if (arr[L] < arr[R]){
// 如果元素 < 基准值,那么放在小于区域的下一个元素,小于区域+1,指针后移
swap(arr,L++,++less);
}else if(arr[L] > arr[R]){
// 如果元素 > 基准值,那么放在大于区域的前一个元素,大于区域+1,指针不变
swap(arr,L,--more);
}else {
// 如果两者相等,那么无需移动,指针直接++
L++;
}
}
//碰撞后,四部分已经形成,那么现在只需要将哨兵位置的元素与>区域的第一个元素进行交换即可
//大于区域进行右减1,因此是more++,因为是索引
swap(arr,R,more);
// 返回数组,只有两个元素,分别标记小于区域的左边界,以及大于区域的右边界
// 记得返回的less 需要 +1 ,因为less记录的是当前小于区域的左边界位置,而需要返回的是
// == 区域的左边界和右边界,所以需要less + 1
return new int[]{less + 1,more};
}
swap算法:
public static void swap(int[] arr,int i,int j){
int temp;
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}