BFPTR算法(求序列中第k小的数)

1973 年,Blum、Floyd、Pratt、Rivest、Tarjan一起发布了一篇名为 “Time bounds for selection” 的论文,给出了一种在数组中选出第k大元素平均复杂度为O(N)的算法,俗称”中位数之中位数算法”。

其思想是基于对快速排序划分过程pivot的值得优化,BFPTR算法选取每5分中位数的中位数进行划分(中位数的中位数算法)

过程:

  1. 将序列从左到右5个5个分组,而且元素个数小于5的组至多有一组
  2. 将每组按降序排序(从大到小),选出每组的中位数,最后一组元素数若为偶数个,则选取较大的元素
  3. 对于步骤2中的中位数递归进行步骤1和步骤2,直到只剩下一个数即为中心数
  4. 基于中心数对序列进行划分(partion),进行高低区的判断后决定是否进行递归

代码实现:


#include <iostream>
#include <algorithm>
const int N = 100;
using namespace std;
bool IsBigger(int a, int b)
{
	return a > b;
}
int Find_mid(int a[], int left, int right)           //查找中位数的中位数
{
	if (left == right)
		return a[left];
	int n = 0;
	int i = 0;
	auto it = a + left;
             //该判断条件决定该for循环只处理个数为5的组,且保证个数小于5的组有且只有一组
	for (i = left; i < right - 5; i += 5,it+=5)
	{
		sort(it, it + 4, IsBigger);            //每5个数按降序排序
		n = i - left;                          //下标为i的数在序列中占第几个位置
		std::swap(a[left + n / 5], a[i+2]);    //将该组数的中位数与序列前排位置交换
		                               //n/5控制前排下标为n/5的与中位数交换
	}                 //循环之后序列前排即是每组数的中位数,有(right-left)/5上取整个
	int num = right - i + 1;
	      //num是个数小于5的组
	      //处理该组数据
	if (num > 0)
	{
		sort(it, it + num - 1, IsBigger);       //降序排序
		n = i - left;                           //同上
		       //该组数给奇数个,取中位数,偶数个,取较大的数交换
		std::swap(a[left + n / 5], a[i + num / 2]); 
	}
	n /= 5;                                     //前排中位数的个数
	if (n <= 1)              //剩余两个中位数,直接返回较小的
		return a[left];
	else                     //超过两个中位数,则继续在中位数中寻找中位数
		return Find_mid(a, left, left + n);
}
int Find_pos(int a[], int left, int right,int mid)    
{                                         //返回该中位数的中位数在a中的下标
	for (int i=left;i!=right+1;i++)
	{
		if (mid == a[i])
			return i;
	}
}
int Partion(int a[], int left, int right,int pos)   //进行划分,类似快速排序的划分过程
{
	std::swap(a[pos], a[left]);     //把中心数放到序列最前面
	int i = left, j = right;
	int pivot = a[left];            //pivot为中心数的值
	while (i < j)        //进行划分过程
	{
		while (i < j && a[j] > pivot)    //从后向前找比pivot小的
			j--;
		a[i] = a[j];
		while (i < j && a[i] < pivot)    //从前向后找比pivot大的
			i++;
		a[j] = a[i];
	}
	               //划分结束后,pivot前面的都是比pivot小的,pivot后面都是比pivot大的
	a[i] = pivot;                        //确定pivot在序列中的位置
	return i;                            //将pivot的下标返回
}
int BFPTR(int a[], int left, int right, int k)      //BFPTR算法
{
	int mid = Find_mid(a, left, right);        //get中心数
	int pos = Find_pos(a, left, right, mid);   //get中心数的下标
	int i = Partion(a, left, right, pos);      //get划分后的中心数的下标
	int m = i - left + 1;                      //确定中心数在该序列中为第m小的数
	if (k == m)
		return a[i];
	if (k < m)                        //在小的区域进行递归查找
		BFPTR(a, left, i - left, k);
	else                              //在大的区域进行递归查找
		                                     //且要查找的为第k-m小的数
		BFPTR(a, i + 1, right, k-m);
}
int main(int argc, char** argv)
{
	int a[N]={ 9,6,3,8,5,2,7,4,1,0 };
	cout << BFPTR(a, 0, 9, 2);
	return 0;
}
结果:1

该算法最坏情况下的时间复杂度为O(n)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值