分治思想--快速选择算法解决最小的k个数(TopK问题)

文章介绍了如何利用分治策略的快速选择算法,在无序数组中高效地找到第k大的元素,避免了完全排序的过程,从而降低了时间复杂度到平均O(n)。通过对快速排序的改进,只需关注包含第k大元素的那一侧子数组,减少了不必要的递归操作。

        

目录

1.问题描述

2.解题思路

2.1实例分析

2.1.1 回顾快速排序

2.1.2 对快速排序的改进

2.1.3 快速选择算法示例

3. 算法的实现


分治,字面意思是“分而治之”,其基本思想为将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

1.问题描述

        在(无序)数组中找到第 k 大 / 小的元素。本文以第k大元素为例)

        输入:数组的大小n和整数k,数组nums

        输出:数组中第k大的元素

        题目来源:力扣215.数组中的第k个最大元素

2.解题思路

2.1实例分析

       最简单粗暴的做法就是直接使用快速排序来解决这个问题,先对原数组进行排序,再返回倒数第k个的位置,快速排序的时间复杂度为O(nlogn),但其实可以通过对其过程中进行优化,是的算法的速度更快。

2.1.1 回顾快速排序

        先来回顾一下快速排序,这是一个经典的分治算法。我们对数组a[l, r]做快速排序的过程是:

·分解:先从要排序数组中选出一个数为基准数,将数组中小于基准数的数放在基准数左边,大于基准数的数放在基准数右边。

·解决:对基准数左右两边数组重复划分,直到每个区间只有一个数。

·合并:因为子数组都是原址排序的,所以不需要进行合并操作,数组已经有序。

2.1.2 对快速排序的改进

        和快速排序不同的是,当寻找TopK时不需要排序一个数组,只要arr[n - k] 左边的值小于等于arr[n - k],右边的值大于等于arr[n - k],无论其划分出的子数组 是否有序,arr[n - k]就是题目要寻找的TopK。也就是说并不需要对左右两部分子数 组都进行递归,而是只对寻找目标所在的子数组进行递归,这就是快速选择算法。

2.1.3 快速选择算法示例

         快速选择算法要点在于划分和选择需要递归的子数组。下面由数组 [3,2,1,5,6,4]对划分进行图解示例(图中有下划线的数字是此次划分的基数)

        第一次划分:

                         

                        找到i和j的位置后交换

                        

       到达边界条件后递归,此时j + 1 = 1,r = 5,此时寻找目标在右部分子数组,所以对右部分子数组进行递归。

        第二次划分:

                        

                        找到i和j的位置后进行交换

                          

        到达边界条件后递归,此时j + 1 = 4,r = 5,此时寻找目标在右部分子数 组,所以对右部分子数组进行递归。

        第三次划分:

                      

        到达边界条件后递归,此时l = 4,j = 4,此时寻找目标在左部分子数组, 所以对左部分子数组进行递归,又l = r,所以递归结束,输出结果。

      

2.2算法的时间复杂度分析

        在每一次进行划分选择的时候都会将总数据分为两个部分,其中一部分的数比选择的数小,另一部分比选择的数大。因此,每次选择的数据规模都会减半。所以其期望时 间复杂度为O(n)。如果每次规模为n的问题都划分成1和n - 1,每次集合又向n -1 集合中递归,这种情况是最坏的,时间复杂度为O(n^{2})。

3. 算法的实现

#include<vector>
#include<iostream>

using namespace std;

const int N = 1e5 + 10;

int n, k, nums[N];

int quick_sort(int q[], int l, int r, int k) {
    if (l == r) return q[l];
    
    // 取中点作为基点
    int i = l - 1, j = r + 1, x = q[l + r >> 1]; 
    while (i < j) {
        do i ++; while (q[i] < x);
        do j --; while (q[j] > x);
        if (i < j) swap(q[i], q[j]);
    }
    
    // 接下来是本次优化的核心,我们不对两个子数组都进行排列,
    // 而是根据k和边界的关系选择对其中一个子数组进行排列
    if (k <= j) return quick_sort(q, l, j, k);
    else return quick_sort(q, j + 1, r, k);
}

int main() {
    // 对读入和输出进行加速
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    // 将题中所给数据进行读入
    cin >> n >> k;
    for (int i = 0; i < n; i ++ ) cin >> nums[i];
    
    printf("%d\n", quick_sort(nums, 0, n - 1, n - k));
    
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值