对于快速排序的理解
- 基本思路:快速排序每一次都排定一个元素(这个元素呆在了它最终应该呆的位置),然后递归地去排它左边的部分和右边的部分,依次进行下去,直到数组有序
第一版代码
import java.util.Random;
class Solution {
private Random random = new Random(System.currentTimeMillis());
public int[] sortArray(int[] nums) {
quickSort(nums, 0, nums.length - 1);
return nums;
}
public void quickSort(int[] nums, int left, int right) {
if(left >= right) return; // 终止条件,left=right的时候只有一个元素不需要排序
int pivotIndex = partition(nums, left, right);
quickSort(nums, left, pivotIndex - 1); // 从左到划分元素前一个做同样操作
quickSort(nums, pivotIndex + 1, right); // 同理
}
public int partition(int[] nums, int left, int right) {
int randomIndex = left + random.nextInt(right - left + 1);
/*
为什么这里要把randomIndex和left进行交换?
保证数组无序性,因为在数组有序的时候出现递归树倾斜问题
此时时间复杂度退化到O(n^2)
*/
swap(nums, left, randomIndex);
int pivot = nums[left];
int j = left; // 循环不变量,代表的永远是第一个区间的最后一个元素
for(int i = left + 1; i <= right; ++i) {
// 先++j,在进行交换
if(nums[i] <= pivot) {
++j;
swap(nums, i, j);
}
}
swap(nums, left, j); // 最后别忘了划分元素和j的交换
return j;
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
根据循环不变量的不同的定义,可以写出不同的代码,比如看你怎么定义j的位置(有的人定义j为第二个区间的第一个元素!)
LeetCode的TopK问题
当使用第一版代码的时候会出现一些问题,当数组中出现大量重复元素的时候,此时只通过取随机数是无效的,要把重复元素均分的分到两个区间,这里只写了双路快排(只会双路快排)
第二版代码
- 双指针:把等于切分元素的所有元素等概率地分到了数组的两侧,避免了递归树倾斜,递归树相对平衡;
public int partition(int[] nums, int left, int right) {
int randomIndex = left + r.nextInt(right - left + 1);
swap(nums, randomIndex, left);
int pivot = nums[left];
int le = left + 1; //循环不变量1,指向第一个区间的最后一个右边
int ge = right;//指向第二个区间的第一个左边
while(true) {
while(le <= ge && nums[le] < pivot) {
le++;
}
while(le <= ge && nums[ge] > pivot) {
ge--;
}
// 退出循环条件
// 最终ge要不然大于le一个,要不然等于le
if(le >= ge) {
break;
}
// 交换le和ge的值,因为le此时指向大于等于pivot,ge指向小于等于pivot
swap(nums, le, ge);
le++;
ge--;
}
/*
这里最终left是一定和ge交换,ge和le相等时,交换谁都可以,但是不相等时
ge指向第一个区间最后一个元素
*/
swap(nums, left, ge);
return ge;
}
总结:收获颇多,写文章是做笔记
推荐看liweiwei1419,讲的更好