BFPRT算法
BFPRT 算法是目前解决 TOP-K 问题最有效的算法,又称为中位数的中位数算法,该算法由 Blum、Floyd、Pratt、Rivest、Tarjan 提出,最坏时间复杂度为 \(O(n)\)。
在一堆数中求其前 k 大或前 k 小的问题,简称 TOP-K 问题。
BFPRT算法
应用场景
线性时间内,从无序列表找到第 k 小的元素。
基本思想
- 首先把数组按 5 个数为一组进行分组,最后不足 5 个的忽略。对每组数进行排序(如插入排序)求取其中位数。
- 把上一步的所有中位数移到数组的前面,对这些中位数递归调用 BFPRT 算法求得他们的中位数。
- 将上一步得到的中位数作为划分的主元进行整个数组的划分。
- 判断第 k 个数在划分结果的左边、右边还是恰好是划分结果本身,前两者递归处理,后者直接返回答案。
算法实现
1 | void bubble_sort(int* a, int left, int right){ |
复杂度分析
划分时以 5 个元素为一组求取中位数,共得到 n/5
个中位数,再递归求取中位数,复杂度为 T(n/5)
。
得到的中位数 x
作为主元进行划分,在 n/5
个中位数中,主元 x
大于其中 1/2*n/5 = n/10
的中位数,而每个中位数在其本来的 5 个数的小组中又大于或等于其中的 3 个数,所以主元 x
至少大于所有数中的 n/10*3 = 3/10*n
个。同理,主元 x
至少小于所有数中的 3/10*n
个。即划分之后,任意一边的长度至少为 3/10
,在最坏情况下,每次选择都选到了 7/10
的那一部分,则递归的复杂度为 T(7/10*n)
。
在每 5 个数求中位数和划分的函数中,进行若干个次线性的扫描,其时间复杂度为 c*n
,其中 c
为常数。其总的时间复杂度满足 T(n) <= T(n/5)+T(7/10*n)+c*n
。
我们假设 T(n) = x*n
,其中 x
不一定是常数(比如 x
可以为 n
的倍数,则对应的 T(n) = O(n^2))
。则有 x*n <= x*n/5+x*7/10*n+c*n
,得到 x <= 10*c
。于是可以知道 x
与 n
无关, T(n) <= 10*c*n
,为线性时间复杂度算法。而这又是最坏情况下的分析,故 BFPRT 可以在最坏情况下以线性时间求得 n
个数中的第 k
个数。