快速选择(基于快速排序算法)

通过前面的快速排序算法我们基本上已经知道,它是通过两个标记点把左边分为大于首元素的部分,右边为小于首元素的部分。然后又再给左右两边用快速排序。

然后现在我们提出一个问题,如果任意给定的一个数组,我们现在要找出它里面第几大的元素。这时该怎么办?先排序吗?你要知道就算是快速排序的复杂度也是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;
    }

    
}

  

 

posted @ 2020-03-18 22:25  Swithun  阅读(529)  评论(0)    收藏  举报