经典算法 BFPRT算法详解
2021/7/21 1:06:08
本文主要是介绍经典算法 BFPRT算法详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
经典算法 BFPRT算法详解
问题描述:给定一个整型数组,返回其中第K小的数
普通解法:
这道题可以利用荷兰国旗改进的 partition 和随机快排的思想:随机选出一个数,将数组以该数作比较划分为 <,=,> 三个部分,则 = 部分的数是数组中第几小的数不难得知,接着对 < (如果第K小的数在 < 部分)或 > (如果第K小的数在 > 部分)部分的数递归该过程,直到 = 部分的数正好是整个数组中第K小的数。这种做法不难求得时间复杂度的数学期望为 O(NlogN) (以2为底)。
但这毕竟是数学期望,在实际工程中的表现可能会有偏差(最坏情况下的时间复杂度会达到O(N^2))
BFPRT算法可以说是这种算法的一种优化吧
public static int process(int[] arr, int L, int R, int index) { if(L == R) { return arr[L]; } //等概率随机选取一个值作划分 L+ [0,R-L] int pivot = arr[L+(int)(Math.random()*(R-L+1))]; //range[0] range[1] // L .... R pivot int[] range = partition(arr,L,R,pivot); if(index >= range[0] && index <= range[1]) { return arr[index]; } else if(index < range[0]) { return process(arr,L,range[0]-1,index); } else { return process(arr,range[1]+1,R,index); } } public static int[] partition(int[] arr,int L, int R, int pivot) { int less = L - 1; int more = R + 1; int cur = L; while(cur < more) { if(arr[cur] < pivot) { swap(arr,++less,cur++); } else if(arr[cur] > pivot) { swap(arr,cur,--more); } else { cur++; } } return new int[] {less + 1, more - 1}; } public static void swap(int[] arr,int i,int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
BFPRT算法
BFPRT算法,又称为中位数的中位数算法,有5位大牛(Blum、Floyd、Pratt、Rivest、Tarjan)提出,并以他们的名字命名。
BFPRT算法能够做到时间复杂度就是 O(N)
BFPRT算法,接收一个数组和一个K值,返回数组中的一个数
-
数组被划分为了 N/5 个小部分,每个部分的5个数排序需要 O(1) ,所有部分排完需要 O(N/5)=O(N)
-
取出每个小部分的中位数,一共有 N/5 个,递归调用BFPRT算法得到这些数中第 (N/5)/2 小的数(即这些数 的中位数),记为 pivot
-
以 pivot 作为比较,将整个数组划分为 <pivot , =pivot , >pivot 三个区域
-
判断第K小的数在哪个区域,如果在 = 区域则直接返回 pivot ,如果在 < 或 > 区域,则将这个区域的数递 归调用BFPRT算法
-
base case :在某次递归调用BFPRT算法时发现这个区域只有一个数,那么这个数就是我们要找的数
// arr[L..R] 如果排序的话,位于index位置的数,是什么,返回什么 public static int bfprt(int[] arr, int L, int R, int index) { if(L == R) return arr[L]; int pivot = medianOfMedians(arr,L,R); int[] range = partition(arr,L,R,pivot); if(index >= range[0] && index <= range[1]) { return arr[index]; } else if(index < range[0]) { return bfprt(arr,L,range[0]-1,index); } else { return bfprt(arr,range[1]+1,R,index); } } //arr[L..R] 五个数一组 //每个小组内部排序 //每个小组中位数拎出来,组成mArr //mArr中的中位数,返回 public static int medianOfMedians(int[] arr, int L, int R) { int size = R - L + 1; int offset = size % 5 == 0 ? 0 : 1; int[] mArr = new int[size / 5 + offset]; for(int team = 0; team < mArr.length; team++) { int teamFirst = L + team *5; mArr[team] = getMedian(arr,teamFirst,Math.min(R, teamFirst)); } return bfprt(mArr,0,mArr.length-1,mArr.length / 2); } //五个数排序,返回中位数 public static int getMedian(int[] arr,int L, int R) { insertionSort(arr,L,R); return arr[(L+R) / 2]; } public static void insertionSort(int[] arr,int L,int R) { for(int i = L +1; i <= R; i++) { for(int j = i - 1; j >= L && arr[j] > arr[j+1]; j--){ swap(arr,j,j+1); } } }
时间复杂度分析:
BFPRT算法在最坏情况下的时间复杂度是O(n),下面予以证明。令T(n)为所求的时间复杂度,则有:
其中:
- T(n/5)来自medianOfMedians(), n个元素,5个一组,共有 n/5 个中位数;
- T(7n/10)来自bfprt(),在 n/5 个中位数中,主元pivot 大于(1/2)*(n/5)=n/10 的中位数,而每个中位数在其本来的5个数的小组中又大于或等于其中的3个数,所以主元pivot 至少大于所有数中的 (n/10)*3=3n/10 个。即划分之后,任意一边的长度至少为 3/10,在最坏情况下,每次选择都选到了 7/10 的那一部分。
- C·n来自其它操作,比如insertionSort(),以及getMedian()和partition()里所需的一些额外操作。
设T(n)=t·n,其中t为未知,它可以是一个正常数,也可以是一个关于n的函数,代入上式︰
其中c为一个正常数,故t也是一个正常数,即T(n)≤10c ·n,因此T(n)=O(n),至此证明结束。
这篇关于经典算法 BFPRT算法详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-16使用vue3+springboot构建简单Web应用教程
- 2024-11-15全栈开发项目实战:从入门到初级项目的实现
- 2024-11-15数据库项目实战:从入门到初级应用教程
- 2024-11-15IDEA项目实战入门教程
- 2024-11-15IT编程项目实战:新手入门的全面指南
- 2024-11-15Java开发项目实战:新手入门与初级技巧
- 2024-11-15Java零基础项目实战:从入门到独立开发
- 2024-11-15MyBatis Plus教程:入门与基础操作详解
- 2024-11-15MyBatis-Plus教程:新手入门与实战技巧
- 2024-11-15MyBatis教程:从入门到实践