BFPRT算法(求前k个小的数)-Java实现

本文详细介绍了BFPRT算法,一种用于求解大数据集中topk问题的最优算法。该算法通过复杂的分组、排序和递归策略,在确保时间复杂度为O(n)的同时,有效地减少了不必要的比较次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近学习了左神BFPRT算法,给大家先讲个段子。

左神说他每次去美国面试,他都会拿BFPRT算法吹一吹。美国5个大佬在一个美丽的地方研究出来这个算法,他说自己热爱算法,他会BFPRT,每次去美国都会怀着朝圣的姿态去在那个地方转转。面试官一听:哇,这么厉害, 过!!!!!!!

 

好了,下来说说这个算法。

BFPRT算法是在进行大量数据排序求topk(前k个最大或最小的数)时最优算法。

为什么叫BFPRT算法,因为是美国5个大佬搞出来的,所以分别取他们的名字命名算法。

 

算法步骤大概如下:

1  将数组划分区域,5个数为一组,把数组分为若干组。(为什么是5呢?因为5能更好的收敛时间复杂度。)

2  将分开的组进行组内排序(这里我用的是插入排序),找出每组数的中位数。

3  找出的若干个中位数,单独放一组进行排序。

4  在找出的中位数组中,递归调用步骤2的过程,找出中位数组的中位数,把这个中位数叫做划分数。

5  用划分数进行partition过程(类似于快速排序的每趟的划分过程):小于划分数放左边,等于划分数放中间,大于划分数放右边。

6  将划分数与k进行比较:

            k<划分数,在步骤5的左边区域递归上述过程,找出左边区域的中位数与k比较,直到与k相等,最后的划分数左边就是前k小的数;

            k=划分数,那么左边区域直接为前k小的数;

            k>划分数,在步骤5的右边区域递归上述过程,找出右边区域的中位数与k比较,直到与k相等,最后的划分数左边就是前k小的数;

 

 

为什么要花费这么多步骤去寻找划分数呢?假设数组长度为n,那么整个数组一共有n/5个中位数,在中位数组找出中位数组的中位数(划分数),那么就会有3*(n/10)个数比划分数小-如图红色区域所示:

我们一次就可以至少刷掉3*(n/10)、最多7*(n/10)的数量级的数据进行递归排序,节省了很多时间!

 

代码实现如下所示:

 

package com.zuoshen;
/** 
 * BFPRT算法求topk,时间复杂度bO(n)
 *
 * @author wanglongfei    
 * E-mail: islongfei@gmail.com
 * @version 2017年8月5日
 * 
 */
public class BFPRT {
	
	    // 得到前k个最小的数
		public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
			if (k < 1 || k > arr.length) {
				return arr;
			}
			int minKth = getMinKthByBFPRT(arr, k);
			int[] res = new int[k];//res前k个结果集
			int index = 0;
			
			for (int i = 0; i != arr.length; i++) {
				if (arr[i] < minKth) {
					res[index++] = arr[i];
				}
			}
			for (; index != res.length; index++) {
				res[index] = minKth;
			}
			return res;
		}
		
		
	   //找出比k小的前k个数
		public static int getMinKthByBFPRT(int[] arr, int K) {
			int[] copyArr = copyArray(arr);
			return select(copyArr, 0, copyArr.length - 1, K - 1);
		}
		
       //复制数组
		public static int[] copyArray(int[] arr) {
			int[] res = new int[arr.length];
			for (int i = 0; i != res.length; i++) {
				res[i] = arr[i];
			}
			return res;
		}
		
		
	      //用划分值与k相比,依次递归排序
		public static int select(int[] arr, int begin, int end, int i) {
			if (begin == end) { //begin数组的开始 end数组的结尾  i表示要求的第k个数
				return arr[begin];
			}
			int pivot = medianOfMedians(arr, begin, end);//找出划分值(中位数组中的中位数)
			int[] pivotRange = partition(arr, begin, end, pivot);
			if (i >= pivotRange[0] && i <= pivotRange[1]) {//小于放左边,=放中间,大于放右边
				return arr[i];
			} else if (i < pivotRange[0]) {
				return select(arr, begin, pivotRange[0] - 1, i);
			} else {
				return select(arr, pivotRange[1] + 1, end, i);
			}
		}
		
		
	     //找出中位数组中的中位数  
		public static int medianOfMedians(int[] arr, int begin, int end) {
			int num = end - begin + 1;              
			int offset = num % 5 == 0 ? 0 : 1;       //分组:每组5个数,不满5个单独占一组
			int[] mArr = new int[num / 5 + offset];  //mArr:中位数组成的数组
			for (int i = 0; i < mArr.length; i++) {  //计算分开后各数组的开始位置beginI 结束位置endI
				int beginI = begin + i * 5;
				int endI = beginI + 4;
				mArr[i] = getMedian(arr, beginI, Math.min(end, endI));//对于最后一组(不满5个数),结束位置要选择end
			}
			return select(mArr, 0, mArr.length - 1, mArr.length / 2);
		}
		
	    //划分过程,类似于快排
		public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
			int small = begin - 1;
			int cur = begin;
			int big = end + 1;
			while (cur != big) {
				if (arr[cur] < pivotValue) {
					swap(arr, ++small, cur++);
				} else if (arr[cur] > pivotValue) {
					swap(arr, cur, --big);
				} else {
					cur++;
				}
			}
			int[] range = new int[2];
			range[0] = small + 1;//比划分值小的范围
			range[1] = big - 1;  //比划分值大的范围
			
			return range;
		}
		
		
	    //计算中位数
		public static int getMedian(int[] arr, int begin, int end) {
			insertionSort(arr, begin, end);//将数组中的5个数排序
			int sum = end + begin;
			int mid = (sum / 2) + (sum % 2);
			return arr[mid];
		}
		
		
	    //数组中5个数排序(插入排序)
		public static void insertionSort(int[] arr, int begin, int end) {
			for (int i = begin + 1; i != end + 1; i++) {
				for (int j = i; j != begin; j--) {
					if (arr[j - 1] > arr[j]) {
						swap(arr, j - 1, j);
					} else {
						break;
					}
				}
			}
		}
		
		
	    //交换元素顺序
		public static void swap(int[] arr, int index1, int index2) {
			int tmp = arr[index1];
			arr[index1] = arr[index2];
			arr[index2] = tmp;
		}
		
		
        //打印结果
		public static void printArray(int[] arr) {
			for (int i = 0; i != arr.length; i++) {
				System.out.print(arr[i] + " ");
			}
			System.out.println();
		}

		public static void main(String[] args) {
			int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
			printArray(getMinKNumsByBFPRT(arr, 10));

		}

}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值