【算法题】BFPRT算法:求第K小或者第K大的数


2017/11/21

BFPRT问题


问题描述:

一个数组中求第k小或者第k大的数

思路

不通过排序求第k小的数,时间复杂度为O(N)。

主要是利用快排中的partition过程。(随机快排见上一篇博客

1、找到一个划分值,按照partition的过程,分为小于区、等于区、大于区,则可知等于区是在整个数组有序后不变的部分。

2、求第K小的数,就是数组有序后下标为k-1的数。

3、所以,如果等于区包含这个k-1,则等于区的数就是第k小的数,直接返回。否则,继续partition过程。

BFPRT的思想是对划分值的选择进行优化,不再是随机选取划分值,而是通过这样一个过程:

1、将数组分为5个一组,不足5个的自动成一组。划分组所用时间为O(1)。

2、将每个组进行组内排序,可用插入排序。因为排序只有5个数,时间复杂度可记为O(1),所有组都排序为O(N)。

3、得到每个组内的上中位数,然后将这些上中位数组成新的数组mediums。

4、求出mediums数组中的上中位数pvalue,不使用排序,用的是递归调用BFPRT的过程,求上中位数就是求mediums数组第mediums.size()/2小的数。

5、此时得到的pvalue就是选取的划分值,然后进行partition过程即可。

为什么要这样选取划分值,这是因为,假设数组长度为n,则mediums数组的长度为n/5,则得到的pvalue在medium中会有n/10的数比其小,而这n/10的数,在自己的5个数的小组中,又会有3个数比pvalue小,所以,至少有n/10*3即3n/10个数比pvalue小,至多有7n/10个数比pvalue大,可以确定的淘汰掉3n/10的数。这样划分比较均衡。

6、刚才拿到pvalue划分值之后,进行partition过程,会返回等于区的边界下标。

7、如果k在等于的范围内,则返回pvalue;k在等于区的左边,则递归调用左边小于区的部分;k在等于区的右边,则递归调用大于区的部分。

代码

#include <iostream>
#include <vector>

using namespace std;
/*
2017/11/18
BFPRT算法:
一个数组中求第k小或者第k大的数
*/
#if 1
#define max(a,b)(a>b?a:b)
#define min(a,b)(a<b?a:b)

int minK(vector<int>&a, int k);
int findKmin(vector<int>&a, int start, int end, int k);
int getMediumOfMedium(vector<int>a, int start, int end);
vector<int> Partition(vector<int>&a, int start, int end, int value);
int getMedium(vector<int>a, int start, int end);
void InsertSort(vector<int>&a, int start, int end);
void myswap(int &a, int &b);

void myswap(int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
}
void InsertSort(vector<int>&a, int start, int end)
{
    if (end == start)
        return;
    for (int i = start + 1; i <= end; i++)
    {
        for (int j = i; j > start; j--)
        {
            if (a[j] < a[j - 1])
                myswap(a[j], a[j - 1]);
        }
    }
}
int getMedium(vector<int>a, int start, int end)
{
    InsertSort(a, start, end);
    return a[start + (end - start) / 2];
}
vector<int> Partition(vector<int>&a, int start, int end, int value)
{
    int less = start - 1;
    int more = end + 1;
    while (start < more)
    {
        if (a[start] < value)
            myswap(a[++less], a[start++]);
        else if (a[start] > value)
            myswap(a[--more], a[start]);
        else
            start++;
    }
    vector<int>p;
    p.push_back(less + 1);
    p.push_back(more - 1);
    return p;
}
int getMediumOfMedium(vector<int>a, int start, int end)
{
    int num = end - start + 1;//有多少个数字
    int flag = num % 5 == 0 ? 0 : 1;//是否有不足5个的为一组
    vector<int>mediums(num / 5 + flag);//中位数数组
    for (int i = 0; i < mediums.size(); i++)
    {
        int istart = start + i * 5;
        int iend = istart + 4;
        mediums[i] = getMedium(a, istart, min(iend, end));
    }
    return findKmin(mediums, 0, mediums.size() - 1, (mediums.size() - 1) / 2);
}
int findKmin(vector<int>&a, int start, int end, int k)
{
    if (start == end)
        return a[start];    
    int pvalue = getMediumOfMedium(a, start, end);
    vector<int>equalIndex = Partition(a, start, end, pvalue);
    if (k >= equalIndex[0] && k <= equalIndex[1])
        return pvalue;
    else if (k < equalIndex[0])
        return findKmin(a, start, equalIndex[0] - 1, k);
    else
        return findKmin(a, equalIndex[1] + 1, end, k);      
}
int minK(vector<int>&a, int k)
{
    if (k<1 || k>a.size())
        return NULL;
    return findKmin(a, 0, a.size() - 1, k - 1);
}

void main()
{
    vector<int>a = { 3, 1, 6, 67, 4, 5, 7, 12, 2, 5, 6, 90, 33 };
    cout << minK(a, 4) << endl;
    system("pause");
}
#else
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值