认识快速排序
快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
不同的是,冒泡排序在每一轮中只把 1 个元素冒泡到数列的一端。而快速排序则在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列的一边,比它小的元素移动到数列的一边,从而把数列拆解成两个部分,这种思路叫做分治法。
如图所示,紫色为基准元素:
快速排序具体实现
从上面的概念中我们可以知道,快速排序中很重要的一个问题 是,基准元素的选取(如何选取基准元素?);还有一个就是基于基准元素对元素进行比较然后移动,从而达到快速排序的目的。
双边循环法
选择基准元素(pivot)
选定了 pivot 之后,并且设置两个指针 left 和 right 指向数列的最左和最优两个元素,如下图 pivot =4:
循环比较交换
- 第 1 次循环:
-
- 从 right 指针开始,让指针所指向的元素和基准元素做比较。如果大于或等于 pivot,则指针向右移动;如果指针小于 pivot ,则 right 指针停止移动,切换到 left 指针。
- 轮到 left 指针行动,让指针所指向的元素和 pivot 做比较。如果小于等于 pivot,则指针向右移动;如果大于 pivot,则 left 指针停止移动(由于 left 指针开始指向的是基准元素,判断肯定相等,所以 left 右移 1 位)。
-
- 左右指针指向的元素交换位置。
如果所示: 由于 7>4,left 指针在元素 7 的位置停下。这时,让 left 指针和 right 指针所指向的元素进行交换,得到的结果如下图所示:
- 第 2 次循环,重新切换到 right 指针,向左移动。right指针先移动到 8 的位置,8>4,继续左移、由于 2<4,则在 2 的位置上停止(此时 right 指针指的位置为 2),left 指针向右移动一位,如果大于 pivot 则停止移动,此时 left 指针指向元素 6;
接下来就是对 4 左右两边的元素进行一个排序,比较交换排序的过程。我们看下代码的实现:
代码实现
/**
* 快速排序
* 双边循环法
* 1. 选定基准元素 pivot
* 2. 设置两个指针,分布为 left,right,指向数列的最左边和最右边
* 3. 开始循环
* 从 right 指针开始,让指针所指向的元素跟基准元素(pivot)进行比较;
* 如果 大于等于 pivot,将 right 指针向左移动(移动后 right = right -1)
* 如果小于 pivot,让 right 指针停止移动,切换到 left 指针
* 此时 left 指针开始移动,让此时的 left 指针所指向的元素和基准元素做比较:
* 如果小于或等于 pivot,则 left 指针向右移动(此时 left = left + 1)
* 如果大于 pivot,让 left 指针停止移动,将 left 指针指向的元素与 right 指针指向的元素进行位置交换
*/
public class QuickSortTwoSide {
public static void main(String[] args) {
int[] nums = new int[]{9,4,7,3,5,6,2,8,1};
quickSortTwo(nums,0,nums.length -1);
System.out.println(Arrays.toString(nums));
}
public static void quickSortTwo(int[] arr,int start,int end){
// 递归的结束条件,start >= end
if(start >= end){
return;
}
// 1. 获取基准元素
int pivotIndex = getPivot(arr,start,end);
// 2. 根据基准元素,分层两个部分进行递归排序
quickSortTwo(arr,start,pivotIndex - 1);
quickSortTwo(arr,pivotIndex + 1,end);
}
private static int getPivot(int[] arr, int start,int end){
// 取第 1 个位置(也可以随机选取位置)的元素作为基准元素
int pivot = arr[start];
int left = start;
int right = end;
while (left != right){
// 控制 right 指针比较并左移
while (left < right && arr[right] > pivot){
right --;
}
// 控制 left 指针比较并右移
while (left < right && arr[left] <= pivot){
left ++;
}
// 如果都不满足的话,交换两个指针指向的元素的位置
if(left < right){
int p = arr[left];
arr[left] = arr[right];
arr[right] = p;
}
}
// 左右指针汇合
arr[start] = arr[left];
arr[left] = pivot;
return left;
}
}
Console 日志信息:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Process finished with exit code 0
时间复杂度:O(nlogn)
单边循环法
单边循环法只从数组的一边进行遍历和交换。首先选定基准元素 pivot,同时,设置一个 mark 指针指向数列的起始位置,这个 mark 指针代表小于 基准元素的区域边界。
循环比较交换
- 从基准元素的下一个位置开始遍历数组:
-
- 如果遍历到的元素大于基准元素,就继续往后遍历
- 如果遍历到的元素小于基准元素,则需要做两件事
-
-
-
- 把 mark 指针右移 1 位,因为小于 pivot 的区域边界增大了 1
-
- 让最新遍历到的元素和 mark 指针所指向的元素交换位置,因为最新遍历的元素归属于小于 pivot 的区域
-
-
- 首先第一遍历得到元素 7 ,7>4,所以继续遍历
- 接下啦第二次遍历得到元素 3, 3<4,所以 mark 指针需要右移 1 位
- 然后,让元素 3 和移位后的 mark 指针指向的元素进行交换(因为元素 3 归属于小于 pivot 的区域)
- 按照这个思路,继续遍历
然后继续递归,最终得到排序好的数列。
代码实现
import java.util.Arrays;
/**
* 快速排序
* 单边循环法
*
*/
public class QuickSortOneSide {
public static void main(String[] args) {
int[] nums = new int[]{9,4,7,3,5,6,2,8,1};
quickSort(nums,0,nums.length -1);
System.out.println(Arrays.toString(nums));
}
public static void quickSort(int[] arr,int start,int end){
// 递归的结束条件,start >= end
if(start >= end){
return;
}
// 1. 获取基准元素
int pivotIndex = getPivot(arr,start,end);
// 2. 根据基准元素,分层两个部分进行递归排序
quickSort(arr,start,pivotIndex - 1);
quickSort(arr,pivotIndex + 1,end);
}
private static int getPivot(int[] arr, int start,int end){
// 取第 1 个位置(也可以随机选取位置)的元素作为基准元素
int pivot = arr[start];
int mark = start; // 遍历的开始位置
for (int i = start + 1; i <= end ; i++) {
if(arr[i] < pivot){
// 如果大于基准元素,继续遍历
mark ++;
int p = arr[mark];
// 此时 mark 位置
arr[mark] = arr[i];
arr[i] = p;
}
}
arr[start] = arr[mark];
arr[mark] = pivot;
return mark;
}
}
console 日志:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
快速排序的时间复杂度是:O(nlogn)