BFPRT算法

  1. 算法介绍:
    BFPRT算法的用处:用于在无序数组中求取第K大或者第K小的数,也就是top(K)问题,对于top(k)问题,最简单的就是排序,之后可以选中,排序最快时间复杂度也只是做到O(n*logn),而BFPRT算法可以将时间复杂度严格控制到O(n)
  2. 实现原理:
    核心:寻找一个合理的划分值p,之后的过程就是快排的Partition过程,判断等于区域(利用的是区域下标来判断)是否命中k,否则向两边的其中一边递归(快速排序的介绍和实现)。和快排的区别就在于快排的划分值我是随机选取的,而BFPRT是有选择性的去选取这个划分值p。
    进入正题:
函数Find_Top_k(arr,k)的功能:在数组arr中找到第k大的数
(1)将给定的数组`arr[N]`划分为多个小组,每5个一组,到最后不足5个元素的单独成组
		只是在逻辑上对数组进行了划分,时间复杂度O(1)
(2)每个组进行组内排序,对5个数进行排序时间复杂度可以是O(1),共N/5组
		只保证组内有序,时间复杂度:O(1)*N/5 = O(N)
(3)得到每个组的“上中位数”,再组成新的数组newarr[](未必是有序的),长度是N/5
		上中位数概念:
		对于奇数个数,就是中位数,		比如 1 2 3 4 5	中位数:3
		对于偶数个数,就是前面的一个数 	比如 6 7 8 9 	上中位数:7
		之后自己调用自己来求得newarr[]的中位数,也就是求中位数数组的中位数mm
		作为划分值p
		时间复杂度:自己调用自己,T(N/5)
(4)Partition过程
		时间复杂度是O(N)	
(5)判断等于区是否命中,未命中则继续向左边或者右边进行递归
		递归规模按最差的情况区估计 T(7N/10)

图示:
在这里插入图片描述
如此复杂的方式来选取划分值p,目的就是为了避免经典做法随机选取一个划分值的不确定性,很有可能就会加大了左右两边递归的规模,时间复杂度就会上升。
在这里插入图片描述
所以总的时间复杂度:T(N) = T(7N/10)+T(N/5)+O(N)
可以证明收敛到O(N) :证明链接

  1. 代码:
#include <iostream>
#include <vector>
using namespace std;

int  GetMedian(vector<int>a,int begin, int end);
int  medianOfMedians(vector<int>a, int begin, int end);
void InsertSort(vector<int>&a,int begin, int end);
int  select(vector<int>&a, int begin, int end, int K);
int  Get_MinKnum_By_BFPRT(vector<int>&a,int K);
vector<int> Partition(vector<int>&a, int l,int r, int pKey);

int main()
{
	//	vector<int>a = {0,13,4,9,10,8,7,5,4,6};		//我的vs2012不支持c++11标准,所以不允许这样赋值,但是devc++可以在编译环境中配置完成
	vector<int>a;
	a.push_back(0);
	a.push_back(13);
	a.push_back(4);
	a.push_back(9);
	a.push_back(10);
	
	a.push_back(8);
	a.push_back(7);
	a.push_back(5);
	a.push_back(4);
	a.push_back(6);
	printf("初始数组a中的元素:");
	for(int i = 0; i<a.size();++i)
		cout<<a[i]<<" ";
	cout<<"\n\n";

	printf(" Get_MinKnum_By_BFPRT 获得的第5大的数是:%d\n\n",Get_MinKnum_By_BFPRT(a,5));


	printf("用于检验:数组a排序后的元素:");
	InsertSort(a,0,a.size()-1);
	for(int i = 0; i<a.size();++i)
		cout<<a[i]<<" ";
	cout<<"\n\n";
	
	return 0;
}


//插入排序
void InsertSort(vector<int>&a,int begin,int end)
{
	if(begin == end)	return;
	for (int i = begin+1; i != end+1; ++i)
	{
		for (int j = i - 1; j >= begin ; j--)
		{
			if(a[j+1] < a[j])
				swap(a[j],a[j+1]);
			else 
				break;
		}
	}
}
//获取中位数
int GetMedian(vector<int>a,int begin, int end)
{
	InsertSort(a,begin,end);
	int sum = begin+end;
	int mid = (sum/2) + (sum%2);	
	return a[mid];
}

//Partition过程
vector<int> Partition(vector<int>&a, int l,int r, int pKey)
{
	int less = l-1;
	int more = r+1;
	int pos  = l;
	while(pos < more)
	{
		if(a[pos] < pKey){
			swap(a[++less],a[pos++]);
		}else if (a[pos] > pKey){
			swap(a[--more],a[pos]);
		}else{
			pos++;
		}
	}
	std::vector<int> range;
	range.push_back(less+1);
	range.push_back(more-1);
	return range;
}

//求取划分值pKey,中位数数组的中位数
int medianOfMedians(vector<int>a, int begin, int end)
{
	int num = end-begin+1;	
	int offset = num % 5 == 0 ? 0 : 1;	//用于不足5个元素自成一组
	std::vector<int> newarr(num/5+offset);
	for (int i = 0; i < newarr.size(); ++i)
	{
		int beginI = begin + i*5;
		int endI = beginI + 4;
		//GetMedian()是获取每组的中位数,之后存到新数组中
		newarr[i] = GetMedian(a,beginI,min(end,endI));	//取min值是因为要处理不足5个一组的情况
	}
	return select(newarr,0,newarr.size()-1,newarr.size()/2);	//获取newarr[]的中位数
	//递归的调用自己求上中位数
}

//select函数:给定一个数组和范围,求位于第k位置上的数
int select(vector<int>&a, int begin, int end, int K)
{
	if(begin == end)
		return a[begin];
	int pKey = medianOfMedians(a,begin,end);
	vector<int> range = Partition(a,begin,end,pKey);
	
	if (K >= range[0] && K <= range[1]){
		return a[K];
	} 
	else if (K < range[0]) {
		return select(a, begin, range[0]-1, K);
	} 
	else {
		return select(a, range[1] + 1, end, K);
	}
}

int Get_MinKnum_By_BFPRT(vector<int>&a,int K)
{
	return select(a,0,a.size()-1,K-1);
}

额,总算磕完了,,,,有点开心^ _ ^

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值