bfprt算法

第k小的数如何在时间复杂度O(N)找到

此类问题,我们运用排序是无法完成的,我们学习的最快的排序时间复杂度为O(N*logN),那我们如何去求解呢,下面给出两种解法。

1.利用快排的思想

我们上篇文章讲到的荷兰国旗问题,来实现快排,我们这个题是不需要对左右边来进行排序的,我们发现通过荷兰国旗划分区间后。
在这里插入图片描述当然我们还是要随机选取这个x,下面就只要小改一下随机快速排序的代码就可以了,下面是代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

//荷兰国旗问题
pair<int, int> getflag(vector<int>&arr, int L, int R)
{
    if (L > R)
    {
        return { -1,-1 };
    }
    if (L == R) return { L,R };
    int less = L - 1;
    int more = R;
    int idx =L;

    //随机取数x=arr[m];
    int m = rand() % (R - L + 1) + L;


    //把它与arr[R]交换
    swap(arr[m], arr[R]);


    while (idx < more)
    {
        if (arr[idx] == arr[R]) idx++;
        else if (arr[idx] < arr[R])
            swap(arr[++less], arr[idx++]);
        else if (arr[idx] > arr[R])
            swap(arr[--more], arr[idx]);
    }
    swap(arr[R], arr[more]);
    return { less + 1,more };
}


int process(vector<int>& arr, int L, int R,int idx)
{
    if (L == R) return arr[L];

    pair<int, int> tmp = getflag(arr, L, R);
    //tmp为等于x的左右边界。
    int left=tmp.first;
    int right=tmp.second;
    
    //如果已经找到
    if(idx>=left&&idx<=right)
    return arr[left];
    //如果在左边区间
    else if(idx<left)
    return process(arr, L, left- 1,idx);
    //如果在右边区间
    else
    return process(arr, right+ 1, R,idx);
}

2.bfprt算法

这个算法是五个大牛的名字的总和,这个算法解决的也是这个荷兰国旗中的x如何选择,才可以保证时间复杂度尽可能的小,这个算法的时间复杂度是O(N)的。

这个算法的定义就是:找出一个无序数组中第k小的数

做法:首先把这个数组按照五个数一组,进行从小到大排序 。再取每个组的中位数(小组元素如果不足五个,奇数个数的同样取中位数,偶数个数取左中位数),把这些数在组成一组。然后再这一组中继续找它的中位数,这个过程可以直接递归套用算法的定义(也就是在新数组中找到第(l+r)/2小的数。)

最后我们就选出了我们的天选之子x,然后通过荷兰国旗问题区划分区间,为啥要这样去选x呢?我们下面来分析时间复杂度

2.1时间复杂度的分析

为了方便理解,我们这里就取5的倍数个数的数组,这样就没有不足五个数的组了,这样分析好理解一些,时间复杂度只会少一些常数项,区别会很大,也没必要这么精确。

假设这个分好组的排序数组,圈中的是中位数组成的数组。
在这里插入图片描述
下面进行分析

在这里插入图片描述

所以我们找出的x,通过荷兰国旗问题划分区间后,左边的区间(小于x的区间)最少有3N/10的个数,右边的区间(大于x的区间)最多有7N/10个数,现在我们用最差情况来估算时间复杂度。

如果现在右区间有7N/10个数,我们的要找的数再右区间,我们在右区间找到这个数的时间复杂度为O(7N/10),最后我们加上排序分组的时间复杂度O(N)+O(N/5),时间复杂度公式T(N)=O(N)+O(N/5)+O(7*N/10),由于这是递归,且这个情况不可以用master公式,我们无法分析,但是数学证明了这个递归的时间复杂度为O(N),但是常数较大。

下面是master公式:

在计算涉及递归的算法的时候,计算复杂度就会变得有些麻烦。Master公式就是用来进行剖析递归行为和递归行为时间复杂度的估算的。
Master公式:T(N) = a*T(N/b) + O(Nd)公式解释:n表示问题的规模,a表示递归的次数也就是生成的子问题数,N/b表示子问题的规模。O(Nd)表示除了递归操作以外其余操作的复杂度结论(证明省略):
①当d<logb a时,时间复杂度为O(Nlogb a)
②当d=logb a时,时间复杂度为O((Nd)*logN)
③当d>logb a时,时间复杂度为O(Nd)
注意:子问题规模必须等分,不管你是分成几部分

下面是代码

pair<int, int> partition(vector<int>& arr, int L, int R,int m)
{
    if (L > R)
    {
        return { -1,-1 };
    }

    if (L == R) return { L,R };
    int less = L - 1;
    int more = R+1;
    int idx = L;
//按照m去分区间
    while (idx < more)
    {
        if (arr[idx] == m) idx++;
        else if (arr[idx] < m)
            swap(arr[++less], arr[idx++]);
        else if (arr[idx] > m)
            swap(arr[--more], arr[idx]);
    }
    return { less + 1,more-1};
}
//找出中间数的函数
int getMedion(vector<int>&tmp,int l,int r)
{
    sort(tmp.begin() +l, tmp.begin()+r+1);
    return tmp[(l+r)/2];
}

int bfprt(vector<int>& arr, int l, int r, int idx);
//找天选之子x的函数
int medianofMedians(vector<int>& arr, int l, int r)
{
    int size = r - l + 1;
    int offset = size % 5 == 0 ? 0 : 1;
    vector<int> tmp(size/5 + offset);


    for (int i = 0; i < tmp.size(); i++)
    {
        int teamfirst = l + i * 5;
        tmp[i] = getMedion(arr, teamfirst, min(r, teamfirst + 4));
    }
    return bfprt(tmp, 0, tmp.size() - 1, tmp.size() / 2);
}

int bfprt(vector<int>&arr,int l,int r, int idx)
{
    if (l == r)
    {
        return arr[l];
    }
    int m = medianofMedians(arr,l,r);
    //荷兰国旗问题
    pair<int, int>tmp= partition(arr, l, r, m);

    
    if (idx >= tmp.first && idx <= tmp.second)
        return arr[tmp.first];
    else if(idx<tmp.first)
        return bfprt(arr, l, tmp.first - 1, idx);
    else 
        return bfprt(arr, tmp.second + 1, r, idx);

}

int minKth(vector<int>arr,int k)
{
//第k个数的下标因该是k-1。
    return bfprt(arr, 0,arr.size()-1,k-1);
}

3.相关题目和总结

数组中第K大的元素
第k大的元素就是第n-k+1小的元素,直接套用bfprt就可以。

如果让你求出从小到大排序后,前k个的元素,我们把第k小的元素找出,然后遍历数组,比他小的元素放入数组中,最后如果还有空缺位置,说明和该元素相等,直接填上这个数就可以了。

  • 28
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

<Augenstern>

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值