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