快速排序!拿下!!!
快速排序是现在编程语言自带排序函数中,使用的最多的算法
例如常见的:
List.sort()
同时,也是面试最喜欢面试的排序算法,没有之一
实现的原理、复杂度都有一定的难度
接下来我们来看看快速排序的实现原理:
在快速排序中,首先我们会随机选中一个数字当作轴,目标是将原始数组根据轴进行分区分拆,比轴小的放在左边,比轴大的放在右边。那么该如何做到这一点呢?如下:
首先,给定一个原始数组 1, 6, 4, 2, 5, 3
。第一步,我们选择哪个数字为轴呢?理论上选择任意值都可以,在这里先选择最后一个元素 3 作为轴
选择轴本身有相应的技巧和方法,此处只考虑简单的情况
紧接着,我们放置左右两个指针,分别指向索引 0 和索引倒数第二个,如图所示:
接下来将除了3以外的数字进行分区,分配到小于3和大于3两个部分
逻辑为:
- 左指针依次往右侧移动,当遇到大于或者等于 3 的数值时停止
- 右指针依次往左侧移动,当遇到小于或者等于 3 的数值时停止
- 将左右两个指针指向的值互换
- 互换以后继续重复上面三个步骤,直到左右两个指针重合
- 将左右两个指针指向的值和轴 3 互换
具体步骤如下图,首先执行1、2两步:
如图:交换了 6 和 2 的位置
接下来继续推进左右指针,发现左右指针在4上重合,所以接下来执行步骤 5,让 4 和 3 交换,如下图所示:
此时,轴 3 已经在正确的位置上,即使3的左右仍是乱序
通过第一次分区,我们将数组分为左右两个部分,按照递归的思想,我们继续将左右两侧数组按照选轴分区重复排序。下面开始第二次分区:
综上不难发现:快速排序的宗旨其实就是通过每次轴分区,都能完成轴的位置定位
时间复杂度
快速排序每一次分区都需要整个数组进行依次比较,时间复杂度为 O(N)
。按照二分法排序一共应该需要 log(N)
次分区,所以最终结果为:O(Nlog(N))
但是存在一种最坏的情况,就是元素的本身都是有序的,那么会发生如图所示这种情况:
这样下去,每次分区其实相当于没有分区,因为轴永远是最大的值,这种情况下的时间复杂度是 O(N^2)
。但是这种情况一般不会出现,只需要了解即可~
代码实现快速排序
将算法用代码实现的时候,需要特别注意总分的思想,不能一开始就去死抠细节。比如,可以思考一下算法逻辑一共可以分为几个大块。首先,快速排序是一个递归思维,既然是递归,就需要知道递归的基准条件和递归公式是什么
基准条件:当数组里元素小于等于1个元素的时候结束
递归公式:当每一次分区,获取到轴位置后,左右分拆数组,继续快速排序
public class QuickSort {
// 快速排序
public static void quickSort(int[] array) {
// 调用快速排序的核心,传入left,right
quickSortCore(array, 0, array.length - 1);
}
// 快速排序的核心其实就是递归函数
public static void quickSortCore(int[] array, int left, int right) {
// 递归基准条件,left >= right 即表示数组只有1个或者0个元素
if (left >= right) {
return;
}
// 根据轴分区
int pivotIndex = partition(array, left, right);
// 递归调用左侧和右侧数组分区
quickSortCore(array, left, pivotIndex - 1);
quickSortCore(array, pivotIndex + 1, right);
}
// 对数组进行分区,并返回当前轴所在的位置
public static int partition(int[] array, int left, int right) {
int pivot = array[right];
int leftIndex = left;
int rightIndex = right - 1;
while (true) {
// 左指针移动
while (array[leftIndex] <= pivot && leftIndex < right) {
leftIndex++;
}
// 右指针移动
while (array[rightIndex] >= pivot && rightIndex > 0) {
rightIndex--;
}
if (leftIndex >= rightIndex) {
break;
} else {
swap(array, leftIndex, rightIndex);
}
}
swap(array, leftIndex, right);
return leftIndex;
}
public static void swap(int[] array, int index1, int index2) {
int temp = array[index1];
array[index1] = array[index2];
array[index2] = temp;
}
}
小贴士:有了
quickSortCore
方法还使用quickSort
方法的原因因为为了方便外部程序调用排序,只需要传入数组就可以了,并不需要再传入
left
、right
。这就是接口友好的一种设计表现