快速排序及其优化
写在前面
最开始学习数据结构中的快排时,使用的是首尾指针方法,但是我自己觉得这个实现起来不方便,因为去学习了别人的方法。
这写方法是看了哔哩哔哩一个up的视频,看完之后感叹太妙了,遂写个文章分享一下。
快慢指针快排
定义fast = 0, slow = 0
,给定数组nums
,初始状态下,定义left = 0, right = nums.length - 1
,遍历数组,每次比较fast
和right
位置的元素,如果nums[fast] < nums[right
,则交换fast
和slow
位置的元素,然后slow++,fast++
,否则只移动快指针fast++
,当fast == right
时停止遍历。遍历完成后再次交换slow
和fast
的位置。
本质上,也是一趟排序一个数,也就是nums[right]
,最后一次交换就是将right
位置的值换到合适的位置,使得左边一定比它小,右边一定比他大。
代码
class Solution {
public static void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
int t = nums[right];
int slow = left, fast = left;
while (fast < right) {
if (nums[fast] < t) {
swap(nums, fast, slow);
slow++;
}
fast++;
}
swap(nums, slow, fast);
quickSort(nums, left, slow - 1);
quickSort(nums, slow + 1,right);
}
public static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
}
但是,由于该方法每次选择的中间值都是nums[right]
,如果数组已经排好序的情况下,则会做很多无用功。在力扣912数组排序中,这个写法会超时。
优化1
使用随机下标替代right,随机生成一个下标作为中间值的下标,再将该下标的值与right下标的值互换。
代码
class Solution {
public static void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
// 生成随机值
Random random = new Random();
int r = random.nextInt(right - left + 1) + left;
swap(nums, r, right);
int t = nums[right];
int slow = left, fast = left;
while (fast < right) {
if (nums[fast] < t) {
swap(nums, fast, slow);
slow++;
}
fast++;
}
swap(nums, slow, fast);
quickSort(nums, left, slow - 1);
quickSort(nums, slow + 1,right);
}
public static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
}
运行时间为:1840ms
这种方法提升了一定的效率,但是,如果遇到数组中有大量相同的值,例如[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1…],则会也会对2进行排序,因此,对于相同的值,我们因该不需要再进行排序。
优化2
为了不对相同的值进行排序,重新定义指针less = left,i = left,great = right
。假设数组中有大量相同的值x
我们需要达到的效果为:less
的左边都是小于x
的值,great右边都是大于x
的值,less
到great
之间都是x
。
代码
class Solution {
public static void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
Random random = new Random();
int r = random.nextInt(right - left + 1) + left;
swap(nums, r, right);
int t = nums[right];
int i = left, less = left;
int great = right;
while (i <= great) {
if (nums[i] < t) {
swap(nums, i, less);
i++;
less++;
} else if (nums[i] > t) {
swap(nums, i, great);
great--;
}else {
i++;
}
}
quickSort(nums, left, less - 1);
quickSort(nums, great + 1,right);
}
public static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
}
运行时间为:39ms
优化3
到这里,快排的效率已经被优化的很不错了,但是还有优化空间,查看JAVA中Arrays.sort()
源码可以知道,当数组元素小于44个时,会直接使用插入排序,如下图:
因此我们也可以写一个插入排序,按照这个逻辑来执行。
代码
class Solution {
public static void insertSort(int[] nums, int left, int right) {
int len = right - left + 1;
int index = left + 1;
while (index <= right) {
for (int i = index;i > left; i--) {
if (nums[i] < nums[i - 1]) {
swap(nums, i, i - 1);
}else {
break;
}
}
index++;
}
}
public static void quickSort(int[] nums, int left, int right) {
if (left >= right) return;
if (right - left + 1 < 44) {
insertSort(nums, left, right);
return;
}
Random random = new Random();
int r = random.nextInt(right - left + 1) + left;
swap(nums, r, right);
int t = nums[right];
int i = left, less = left;
int great = right;
while (i <= great) {
if (nums[i] < t) {
swap(nums, i, less);
i++;
less++;
} else if (nums[i] > t) {
swap(nums, i, great);
great--;
}else {
i++;
}
}
quickSort(nums, left, less - 1);
quickSort(nums, great + 1,right);
}
public static void swap(int[] arr, int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
}
运行时间为:22ms
以下是我的leetcode运行截图: