排序算法千千万,今天先讲快排!!
关于「详解归并排序及其应用」,可见 详解归并排序及其应用
「快排」其实是先把一个元素的位置排好,然后递归该元素的两边即可
当我们把第一个元素的位置安顿好后,剩余的元素只需要递归处理即可,分别递归区间[lo, j - 1]
和[j + 1, hi]
现在来分析一下「快排」的时间复杂度,先看图:
对于长度为n
数组来说,树的每一层处理时间为n
;如果树趋于一棵平衡二叉树,那么树高为logn
所以最终时间复杂度为o(nlogn)
但是,存在一种特殊情况,如果这颗树长歪了??如下图所示:
那么,此时的树高变为了n
,时间复杂度退化为o(n^2)
出现这种情况的例子也很简单,如果原数组已经有序,那么就会出现这种情况,不信可以手动模拟一下!!
如何解决这种情况呢?
注意:快排是一种「不稳定」排序;归并是一种「稳定」排序!!
「不稳定」表示值相等的元素可能会被改变原来的顺序
xpublic class QuickSort {
private final Random random = new Random();
public void sort(int[] nums) {
// 方法一:打乱
shuffle(nums);
sort(nums, 0, nums.length - 1);
}
private void sort(int[] nums, int lo, int hi) {
if (lo >= hi) return ;
int p = partition(nums, lo, hi);
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
private int partition(int[] nums, int lo, int hi) {
// 方法二:随机取
int p = random.nextInt(hi - lo + 1) + lo;
swap(nums, lo, p);
int pivot = nums[lo];
int i = lo + 1, j = hi;
while (i <= j) {
// while 结束后,[lo, i) 均 <= pivot
while (i < hi && nums[i] <= pivot) i++;
// while 结束后,(j, hi] 均 > pivot
while (j > lo && nums[j] > pivot) j--;
// ------ 若为降序,只需更改此处两行代码即可 ------
// while (i < hi && nums[i] >= pivot) i++;
// while (j > lo && nums[j] < pivot) j--;
// ------------------- end -------------------
if (i >= j) break;
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
private void shuffle(int[] nums) {
Random random = new Random();
int n = nums.length;
for (int i = 0; i < n; i++) {
int r = i + random.nextInt(n - i);
swap(nums, i, r);
}
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
}
最简单的应用肯定就是给一个数组,然后排序。如题目 排序数组
今天来介绍几个难「亿」点的题目
题目详情可见 数组中的第K个最大元素
当我们安顿的元素位置恰好为K
时,那么该元素即为数组中的第 K 个最小元素
xxxxxxxxxx
private int k;
private Random random = new Random();
public int findKthLargest(int[] nums, int k) {
int n = nums.length;
// 转化为第 n - k + 1 小
this.k = n - k + 1;
return sort(nums, 0, n - 1);
}
private int sort(int[] nums, int lo, int hi) {
int p = partition(nums, lo, hi);
// 向左边递归
if (p > k - 1) return sort(nums, lo, p - 1);
// 向右边递归
if (p < k - 1) return sort(nums, p + 1, hi);
// 找到!!
return nums[p];
}
private int partition(int[] nums, int lo, int hi) {
int p = random.nextInt(hi - lo + 1) + lo;
swap(nums, lo, p);
int pivot = nums[lo];
int i = lo + 1, j = hi;
while (i <= j) {
while (i < hi && nums[i] <= pivot) i++;
while (j > lo && nums[j] > pivot) j--;
if (i >= j) break;
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
private void swap (int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
看到很多人说面试会问这种写法的时间复杂度,先给出答案o(n)
基础的快排框架下,每一层需要处理的节点数都为n
,而这个题目类似于二分,每次只需要处理一半的节点数量
假设在完美的情况下,partition()
中返回的p
都是中间值,这样就可以完美平分节点数量
所以第一次需要处理
综上,一共需要处理
根据等比数列求和公式:
所以一共需要处理 o(n)
题目详情可见 剑指 Offer 40. 最小的k个数
和上一题大同小异,当我们安顿的元素位置恰好为K
时,那么该元素及其左边的元素即为数组中最小的 k 个数
xxxxxxxxxx
private int k;
private Random random = new Random();
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0 || arr.length == 0) return new int[0];
this.k = k;
return sort(arr, 0, arr.length - 1);
}
private int[] sort(int[] nums, int lo, int hi) {
int p = partition(nums, lo, hi);
if (p > k - 1) return sort(nums, lo, p - 1);
if (p < k - 1) return sort(nums, p + 1, hi);
return Arrays.copyOf(nums, k);
}
private int partition(int[] nums, int lo, int hi) {
int p = random.nextInt(hi - lo + 1) + lo;
swap(nums, lo, p);
int pivot = nums[lo];
int i = lo + 1, j = hi;
while (i <= j) {
while (i < hi && nums[i] <= pivot) i++;
while (j > lo && nums[j] > pivot) j--;
if (i >= j) break;
swap(nums, i, j);
}
swap(nums, lo, j);
return j;
}
private void swap (int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}