美文网首页
[算法详解][快速选择] Quick Select

[算法详解][快速选择] Quick Select

作者: 奔跑的程序媛A | 来源:发表于2019-06-09 07:51 被阅读0次


    通常用来在未排序的数组中寻找第k小/第k大的元素。

    【基本思想】

    选择一个元素作为基准来对元素进行分区,将小于和大于基准的元素分在基准左边和右边的两个区域。快速选择并不递归访问双边,而是只递归进入一边的元素中继续寻找。

    【步骤】

    1. 选择一个元素作为基准来对元素进行分区
    2. 将小于和大于基准的元素分在基准左边和右边的两个区域。
    3. 递归进入一边的元素中继续寻找

    【实例分析】

    nums = [1, 5, 1, 1, 6, 4], k=2
    left = 0, right = 5, pivot = nums[2] = 1, cur = 0

    • loop1: nums = [1, 5, 1, 1, 6, 4]
      nums[cur] = 1 == pivot, cur++. cur = 1, left = 0, right = 5
    • loop2: nums= [1, 4, 1, 1, 6, 5]
      nums[cur] = 5 > pivot, swap nums[right] and pivot, , right=4, left = 0, cur = 1
    • loop3: nums = [1, 6, 1, 1, 4, 5]
      nums[cur] = 4 > pivot, swap nums[right] and pivot, , right=3, left = 0, cur = 1
    • loop4: nums = [1, 1, 1, 6, 4, 5]
      nums[cur] = 6 > pivot, swap nums[right] and pivot, , right=2, left = 0, cur = 1
    • loop5: nums = [1, 1, 1, 6, 4, 5]
      nums[cur] = 1 == pivot. cur = 2, left = 0, right = 2
    • loop6:
      cur == right break while loop.

    left 表示小于pivot的个数,cur表示小于等于pivot的个数,k为前k个最小元素
    如果left >= k,遍历左半部分,即select(nums, start, left, k)
    如果cur<=k,遍历右半部分 select(nums, cur, end, k)

    该实例需要遍历右半部分,select(nums, 2, 5, 2)
    left = 2, right = 5, pivot = nums[3] = 6, cur = 2
    nums = [1, 1, 1, 6, 4, 5]
    loop1: nums = [1, 1, 6, 1, 4, 5]
    nums[cur] = 1 < pivot,swap nums[left] and pivot, right=5, left = 3, cur = 3
    loop2: nums = [1, 1, 1, 6, 4, 5]
    nums[cur] = 6 == pivot. right=5, left = 3, cur = 4
    loop2: nums = [1, 1, 1, 4, 6, 5]
    nums[cur] = 4 < pivot,swap nums[left] and pivot, right=5, left = 4, cur = 5
    loop3:
    cur == right break while loop.
    遍历左半部分,select(nums, 2, 4, 2)
    left = 2, right = 4 pivot = nums[3] = 4, cur = 2
    nums = [1, 1, 1, 4, 6, 5]
    loop1: nums = [1, 1, 1, 4, 6, 5]
    nums[cur] = 1 < pivot,swap nums[left] and pivot, right=4, left = 3, cur = 3
    loop2: nums = [1, 1, 1, 6, 4, 5]
    nums[cur] = 4 == pivot 。right=4, left = 3, cur = 4
    loop3:
    cur == right break while loop.

    遍历左半部分,select(nums, 2, 3, 2)
    left = 2, right = 3 pivot = nums[2] = 1, cur = 2
    nums = [1, 1, 1, 6, 4, 5]
    loop1: nums = [1, 1, 1, 4, 6, 5]
    nums[cur] = 1 == pivot,right=3, left = 2, cur = 3

    遍历左半部分,select(nums, 2, 2, 2)
    return

    此时nums[k]为median

    【伪代码】

    function select(list, left, right, k)
          if left = right
                 return 
          pivot = list[mid]
         index: cur, left_cur, right_cur
         loop
             if list[cur] > pivot
                 swap(list[cur] , list[right_cur]);
                 right--
             else if list[cur] < pivot
                 swap(list[cur] , list[left_cur]);
                  left++; cur++
             else
                 cur++
        if(k <= left) select(list, left, left_cur, k)
        if(k > cur) select(list, cur, right, k)
    

    【JAVA代码实现】

        public void median(int[] nums, int start, int end, int k) {
            int left = start, right = end;
            if(left == right) return;
            int cur = left;
            int pivot = nums[(left + right) / 2];
            while(cur < right){
                if(nums[cur] < pivot) {
                    int tmp = nums[cur];
                    nums[cur] = nums[left];
                    nums[left] = tmp;
                    cur++;
                    left++;
                }else if(nums[cur] > pivot) {
                    int tmp = nums[cur];
                    nums[cur] = nums[right];
                    nums[right] = tmp;
                    right--;
                }else{
                    cur++;
                }
            }
            if(left >= k) median(nums, start, left, k);
            if(cur <= k) median(nums, cur, end, k);
            return;
        }
    

    【性能分析】

    1. 最优
    时间复杂度为O(n)
    在最好情况下,若输入已排序,插入排序的时间复杂度在O(n),即线性时间上完成排序。
    2. 最坏
    时间复杂度为O(n^2)
    3. 平均
    O(n)
    因为Quick select只考虑所寻找的目标所在的那一部分子数组
    设算法时间复杂度为T(n)。在第一层循环中,我们将pivot与n个元素进行了比较,这个时间为cn 。
    所以第一步时间为:T(n) = cnc + T(n/2)。其中T(n/2)为接下来递归搜索其中一半的子数组所需要的时间。
    在递归过程中,我们可以得到如下公式:
    T(n/2) = c(n/2) + T(n/4)
    T(n/4) = c(n/4) + T(n/8)
    ...
    T(2) = 2c + T(1)
    T(1) = 1
    c
    将以上公式循环相加消除T项可以得到:
    T(n) = c(n + n/2 + n/4 + n/8 + ... + 2 + 1) = 2n = O(n)

    4. 空间复杂度
    O(1)
    算法没有使用额外空间,swap操作是inplace操作,所以算法的空间复杂度为O(1)。

    5. 稳定性

    【应用:常见面试题目】

    Leetcode 324. Wiggle Sort II

    参考:Quick Sort

    相关文章

      网友评论

          本文标题:[算法详解][快速选择] Quick Select

          本文链接:https://www.haomeiwen.com/subject/pzkgxctx.html