快速选择(基于快速排序算法)
通过前面的快速排序算法我们基本上已经知道,它是通过两个标记点把左边分为大于首元素的部分,右边为小于首元素的部分。然后又再给左右两边用快速排序。
然后现在我们提出一个问题,如果任意给定的一个数组,我们现在要找出它里面第几大的元素。这时该怎么办?先排序吗?你要知道就算是快速排序的复杂度也是nlogn。那有没有更简单的呢???答案是肯定有的!!!
其实我们再返回去仔细观察一下快速排序,如果我们把它使用在现在这个问题上,会发现它会做很多无用功。因此我们根据快速排序,引出了快速选择算法,它的复杂度只有n。
如果现在我们有一个数组[3,6,4,1,5]它的第二大元素是5。那我们如何通过快速选择算法选择出5呢?下面是算法核心思想。
第一:和快速排序算法一样,设定一个标准值,为arr[left],即等于3.然后设定两个标签i,r分别指到6,5.如果i指向的值大于标准值,那我们就向后移动(反之就是要保证左边是大于标准值的元素,右边是小于标准值的元素),如果小于标准值就停止移动。然后再移动j,如果j小于标准值就向前移动,如果大于标准值就停止移动,然后和i指向的那个值进行元素交换。第一次执行完结果是:3,6,4,5,1.
第二:交换完了,此时那i,r是一直移动吗???当然不是首先要保证i<=r的时候才移动,因为如果r>i说明左边和右边的元素都比过一边了,没必要再继续比了(注意交换完了的时候i++,r--方便下次比较。)如果i不满足<=r的情况,那我们就给r现在指的值和标准值交换,因此现在的结果是5,6,4,3,1.
但是为什么要将r指向的值和标准值交换呢?首先要考虑两个问题:1.r一旦>i说明r现在刚刚进入全是比标准值大的区域了,因此在这个区域标准值是最小的,左边全是大于它的,右边全是小于它的,因此r现在这个位置就是个分水岭。在回到题目,找出第二大元素5,第二大,这个时候肯定全在左边,因为左边现在比3大的都有3个。但是如果是第5大的呢,其实这个时候就应该去右边找了。因此我们给出下面的结论
k=r,枢纽元素就是要找的元素,直接返回。
k<r,要找的元素在枢纽元素的左边,递归调用partition在左边序列中找第k个顺序统计量。
k>r,要找的元素在枢纽元素的右边,递归调用partition在右边序列中找第k-i个顺序统计量。
2.如果我们递归到[5,6,4]左边了呢?现在i指向的是6,r指向的是4,满足条件i--,r++。如果现在我们拿标准值5去和i交换,这个时候我们没法保证这个数组里面第2个元素取出来,就是我们总数组里的第二大元素,所以只能和r交换,还是要保证它的有序性。变成:6,5,4.因此这个数组里面的第二大元素就是总数组的第二大元素。
上代码!!!
class Solution {
/**
* 解法0. 堆 O(klogn)
* 解法1. 快速选择: O(n)
*/
public int findKthLargest(int[] nums, int k) {
if (nums.length == 0 || nums == null) return 0;
int left = 0, right = nums.length - 1;
while (true) {
int position = partition(nums, left, right);
if (position == k - 1) return nums[position]; //每一轮返回当前pivot的最终位置,它的位置就是第几大的,如果刚好是第K大的数
else if (position > k - 1) right = position - 1; //二分的思想
else left = position + 1;
}
}
private int partition(int[] nums, int left, int right) {
int pivot = left;
int l = left + 1; //记住这里l是left + 1
int r = right;
while (l <= r) {
while (l <= r && nums[l] >= nums[pivot]) l++; //从左边找到第一个小于nums[pivot]的数
while (l <= r && nums[r] <= nums[pivot]) r--; //从右边找到第一个大于nums[pivot]的数
if (l <= r && nums[l] < nums[pivot] && nums[r] > nums[pivot]) {
swap(nums, l++, r--);
}
}
swap(nums, pivot, r); //交换pivot到它所属的最终位置,也就是在r的位置,因为此时r的左边都比r大,右边都比r小
return r; //返回最终pivot的位置
}
private void swap(int[] nums, int l, int r) {
int tmp = nums[l];
nums[l] = nums[r];
nums[r] = tmp;
}
}

浙公网安备 33010602011771号