我自己搭建了博客,以后可能不太在CSDN上发博文了,https://www.qingdujun.com/ 。


去年写了一篇《分治算法 求第kkk小元素 O(n)O(n)O(n) & O(nlog2nO(nlog2^nO(nlog2n)》的文章。介绍了一种对快排进行改进的算法,可以在时间复杂度O(n)O(n)O(n)的情况下,找到第kkk小的数字。

那时候,我还不知道这个算法叫BFPRT算法——现在知道了,还知道它又被称为中位数的中位数算法,它的最坏时间复杂度为O(n)O(n)O(n) ,它是由Blum、Floyd、Pratt、Rivest、Tarjan提出,它的思想是修改快速选择算法的主元选取方法,提高算法在最坏情况下的时间复杂度。

1 STL std::nth_element

而且,我还发现了STL中有一个类似的函数——std::nth_element (位于头文件<algorithm>中):
http://www.cplusplus.com/reference/algorithm/nth_element/

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>int main() {std::vector<int> v{5, 6, 4, 3, 2, 6, 7, 9, 3};std::nth_element(v.begin(), v.begin() + v.size()/2, v.end());std::cout << "The median is " << v[v.size()/2] << '\n';std::nth_element(v.begin(), v.begin()+1, v.end(), std::greater<int>());std::cout << "The second largest element is " << v[1] << '\n';
}

The median is 5
The second largest element is 7

2 BFPRT算法(中位数的中位数算法)

好了,言归正传。BFPRT算法主要由两部分组成(快排、基准选取函数)。基准选取函数也就是中位数的中位数算法(Median of Medians algorithm)的实现,具体来说——就是将快排中基准选取策略进行了优化,改为每次尽可能的选择中位数作为基准。

那么是如何尽可能的选出中位数? 如果要找到一个精确的中位数,所消耗的时间代价将得不偿失,而且对于快排算法来说,只要基准尽可能的接近真正的中位数,就能形成近似的均分。我在上一篇文章中举了个例子,这里我再重复一遍:

2.1 举个例子

假设,我们要找arr[18]的近似中位数——其实,也就是找到数字8。(注意到,由于使用了分组,这里产生的只是尽可能的中位数)

int arr[18] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 };

BFPRT算法规定了,将5个元素作为一组(选出中位数)。然后,再选出中位数中的中位数…一直到,最终选出一个数字。

那么,第一轮就是这样(将选出的中位数放置在最前面):

//执行momedians之前
{ 1,2,3,4,5} {6,7,8,9,10} {11,12,13,14,15} {16,17,18 };

分别对每组sort后选取小组中位数,再使用swap将小组中位数放置在“最前面”对应位置(注意下文中*号的标注,它表示这一步选出的小组中位数放置到哪儿去了)。

//开始momedians
{ 3*,2,1*,4,5};//sort->swap(3,1)
{ 3,8*,1,4,5} {6,7,2*,9,10};//sort->swap(8,2)
{ 3,8,13*,4,5} {6,7,2,9,10} {11,12,1*,14,15};//sort->swap(13,1)
{ 3,8,13,17*,5} {6,7,2,9,10} {11,12,1,14,15} {16,4*,18 };//sort->swap(17,4)

同样,第二轮初始时就成如下样子了,很显然已经少于5个数字了:

//执行momedians之前
{ 3,8,13,17};

直接选出中位数8

2.2 算法实现(c++)

可以将上述过程使用C++代码描述如下(对于5个数字的排序,使用Insert sort可能会效率更高)。再次强调,下面这段代码唯一的作用,就是用来选出每次快排的基准:

//Median of Medians algorithm
int momedians(int* const low, int* const high) {if (low >= high - 1) {return *low;}//int grp_length = (int)std::sqrt(high - low);int grp_length = 5, grp_idx = 0;for (int* l = low, *r; l < high; l = r+1) {r = (l + grp_length >= high) ? high : l + grp_length - 1;std::sort(l, r); //可以使用下文的void isort(int* const low, int* const high)std::swap(*(low+grp_idx++), *(l + (r - l) / 2));}return momedians(low, low + grp_idx);
}

写到这里已经将BFPRT算法核心介绍完毕了。

如果想测试使用Insert sort是否会带来效率上提升的小伙伴,可以试试下面这段代码insert_sort,尝试着替换文中的std::sort排序函数。

//Insert sort
void insert_sort(int* const low, int* const high) {for (int* i = low + 1; i <= high; ++i) {if (*i < *(i-1)) {int border = *i, *j = i-1;for ( ; border < *j; --j) {*(j+1) = *j;}*(j+1) = border;}}
}

让我们思维次再回到momedians函数。从上述代码中可以看到grp_length = 5是一个固定值。那么,在BFPRT算法中,为什么是选5个作为分组? 这个我也不是很明白,我也尝试使用sqrt(数组长度)作为分组的长度。

有兴趣的可以阅读 ACdreamer 的一篇《BFPRT算法原理》,他在文章结尾处,对为什么使用5作为固定分组长度进行了简单说明。同时,还附有BFPRT算法的最坏时间复杂度为 O(n)O(n)O(n) 的证明。

好了,以下为完整的“BFPRT算法:时间复杂度O(n)O(n)O(n)求第k小的数字”代码。如上文所说,算法主体功能是快排,只是在基准选取的时候使用了momedians算法——而不是直接取第一个数作为基准(严蔚敏版教材中的做法)。

#include <iostream>
#include <algorithm>
using namespace std;//Median of Medians algorithm
int momedians(int* const low, int* const high) {if (low >= high - 1) {return *low;}int grp_length = 5, grp_idx = 0;for (int* l = low, *r; l < high; l = r+1) {r = (l + grp_length >= high) ? high : l + grp_length - 1;std::sort(l, r);std::swap(*(low+grp_idx++), *(l + (r - l) / 2));}return momedians(low, low + grp_idx);
}//Quick sort
int qsort(int* const low, int* const high, int* const ptrk) {int* l = low, *r = high;if (l < r) {int pivot = momedians(l, r);while (l < r) {while (l < r && *r >= pivot) {--r;}*l = *r;while (l < r && *l <= pivot) {++l;}*r = *l;}*r = pivot;}//per qsort end, check left == right == ptrk?return r == ptrk ? *ptrk :(r > ptrk ?qsort(low, r - 1, ptrk) :qsort(r + 1, high, ptrk));
}//Blum、Floyd、Pratt、Rivest、Tarjan
int bfprt(int* const low, int* const high, const int k = 1) {if (low >= high || k < 1 || k > high - low) {throw std::invalid_argument("low > high || k < 1");}return qsort(low, high-1, low + k -1);//[low, high)
}int main() {int arr[18] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 };//cout << bfprt(&arr[0], &arr[0]+1, 1) << endl;cout << bfprt(&arr[0], &arr[0] + 18, 18) << endl;return 0;
}

以上的代码是针对int数组编写的,下面再测试一下对于其他类型的支持情况(这里直接粗暴的加上了一个模板)。有一点想强调的是,这里的bfprt函数参数是左闭右开区间[low,high),同时k必须是从1开始的正数。

//Blum、Floyd、Pratt、Rivest、Tarjan
template<typename T>
T bfprt(T* const low, T* const high, const int k = 1) {if (low >= high || k < 1 || k > high - low) {throw std::invalid_argument("low > high || k < 1");}return qsort(low, high-1, low + k -1);//[low, high)
}

main函数中对intcharstring类型进行了测试。完整代码如下…

#include <iostream>
#include <algorithm>
using namespace std;//Median of Medians algorithm
template<typename T>
T momedians(T* const low, T* const high) {if (low >= high - 1) {return *low;}int grp_length = 5, grp_idx = 0;for (T* l = low, *r; l < high; l = r+1) {r = (l + grp_length >= high) ? high : l + grp_length - 1;std::sort(l, r);std::swap(*(low+grp_idx++), *(l + (r - l) / 2));}return momedians(low, low + grp_idx);
}//Quick sort
template<typename T>
T qsort(T* const low, T* const high, T* const ptrk) {T* l = low, *r = high;if (l < r) {T pivot = momedians(l, r);while (l < r) {while (l < r && *r >= pivot) {--r;}*l = *r;while (l < r && *l <= pivot) {++l;}*r = *l;}*r = pivot;}//per qsort end, check left == right == ptrk?return r == ptrk ? *ptrk :(r > ptrk ?qsort(low, r - 1, ptrk) :qsort(r + 1, high, ptrk));
}//Blum、Floyd、Pratt、Rivest、Tarjan
template<typename T>
T bfprt(T* const low, T* const high, const int k = 1) {if (low >= high || k < 1 || k > high - low) {throw std::invalid_argument("low > high || k < 1");}return qsort(low, high-1, low + k -1);//[low, high)
}int main() {int arr[18] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18 };cout << bfprt(&arr[0], &arr[0] + 18, 18) << endl;char str[7] = {'a','b','c','d','e','f','g'};cout << bfprt(&str[0], &str[0] + 7, 4) << endl;string s = "abcdefghijklmnopqrstuvwxyz";cout << bfprt(&s[0], &s[0] + 26, 10) << endl;return 0;
}

©qingdujun
2018-12-25 北京 海淀

References:
[1] 为径,分治算法 求第k小元素 O(n)O(n)O(n) & O(nlog2n)O(nlog2^n)O(nlog2n) ,2018-12-25
[2] ACdreamer,BFPRT算法原理 ,2018-12-25
[3] STL nth_element() , 2018-12-25

BFPRT算法:时间复杂度O(n)求第k小的数字(分治算法+快排)相关推荐

  1. 【最详细】BFPRT算法:时间复杂度O(n)求第k小的数字

    去年写了一篇对快排进行改进的算法,可以在时间复杂度 O(n)O(n)O(n)的情况下,找到第kkk小的数字. 那时候,我还不知道这个算法叫BFPRT算法--现在知道了,还知道它又被称为中位数的中位数算 ...

  2. 找到数组中第k小的值(利用快排的划分函数)

    在数组中找到第K小的值,我们利用划分函数,可以做到和快排异曲同工的效果. 话不多说,先上在快排中的划分函数代码: int Parition(vector<int>& ar, int ...

  3. 分治算法 求第k小元素 O(n) O(nlog2^n)

    BFPRT算法:时间复杂度O(n)求第k小的数字(分治算法+快排) 各位小伙伴,由于本篇文章代码太过杂乱.我于 2018年12月25日 对文中介绍的算法进行了重写.点击上面的蓝色字体,可以阅读重写后的 ...

  4. 借组磁带机求第K小元素

    如果输入在磁带机上, 你的机器只有一个磁带机驱动器和几十字的内存,如何找第K小的数 1. 遍历一遍磁带,随即选择一个数M 2. 再遍历一遍磁带, 计算大于和小于M的个数,这样就可以获得数M在总序列中的 ...

  5. 两个有序数组合起来求第k小的数+左老师专访ACM大神(笔记)8月5日斗鱼直播实录

    1.长度相等的两个有序数组寻找上中位数 注:上中位数1 2 3 4 5 6为3(偶数两个中位数为前面那个) 思路:去掉不可能为上中位数的,剩下的简化组合求上中位数. 1.1 奇数序列 位置 位置 位置 ...

  6. 数字n,按字典排序,找出第k小的数字

    (1)首先列出所有的字典序,然后选择第k个 class Solution { public:     bool static compare(int a,int b)     {         re ...

  7. 九度OJ 题目1534:数组中第K小的数字(二分解)

    题目链接:点击打开链接 题目描述: 给定两个整型数组A和B.我们将A和B中的元素两两相加可以得到数组C. 譬如A为[1,2],B为[3,4].那么由A和B中的元素两两相加得到的数组C为[4,5,5,6 ...

  8. Leetcode 440.字典序第k小的数字

    字典序第k小的数字 给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字. 注意:1 ≤ k ≤ n ≤ 109. 示例 : 输入: n: 13 k: 2 输出: 10 解释: 字典序的排 ...

  9. A*算法的认识与求第K短路模板

    现在来了解A*算法是什么 现在来解决A*求K短路问题 在一个有权图中,从起点到终点最短的路径成为最短路,第2短的路成为次短路,第3短的路成为第3短路,依此类推,第k短的路成为第k短路.那么,第k短路怎 ...

最新文章

  1. python 按键获取_Python中按键来获取指定的值
  2. 如何查看SharePoint未知错误
  3. httprunner框架学习总结
  4. (论坛答疑点滴)怎么在datagrid翻页的时候确认?
  5. 推荐一个硬核嵌入式的原创公众号
  6. 2018 年“浪潮杯”山东省 ACM 省赛总结
  7. Python(65)_写函数,判断用户传入列表的长度,若大于2,则仅保留前两个长度的内容,并将其返回给调用者...
  8. 柳传志退休后拿近1亿薪酬?联想回应了:严重失实
  9. Highlight(高亮)Gridview控件列
  10. 2020中国移动创客马拉松大赛移动云专题赛决赛成功举办
  11. SSM框架(Spring + Spring MVC + Mybatis)搭建
  12. spring核心知识点分析
  13. Linux系统编程思维导图:基础指令,常用工具,进程,基础IO,IPC,线程;思维导图因为图片过大所以放了链接,需要的可以下载
  14. LS-DYNA 学习总结与感悟 以EM模块为主
  15. java 过载_过载保护【转载】
  16. box-shadow单边阴影设置
  17. 2022考研资料每日更新(2021.05.07)
  18. 关于flash强制更新:早上上班,多台电脑提示未安装flash
  19. python文字处理dummy_python学习之使用multiprocessing.dummy多线程爬虫
  20. 鲸探APP处罚60余位转售数字藏品用户 | 产业区块链发展周报

热门文章

  1. 商务网站建设与维护【11】
  2. 集成微控制器使太阳能微型逆变器设计成本有效
  3. 88---Python 以符号的方式给出积分表达式,类似Mathematics
  4. 手机直播/PC直播/摄像头直播等流媒体直播系统中如何进行推流优化?
  5. 网上书城项目的需求分析、数据库表设计及前端界面的编写(项目进度一)
  6. 深度学习AI美颜系列——人像静态/动态贴纸特效算法实现 | CSDN博文精选
  7. Html网页远程控制电脑,如何从Web浏览器远程控制您的计算机 | MOS86
  8. [Jule CTF 2022] 部分WP
  9. CentOS挂载3T硬盘的方法
  10. android获取ro._Android 简单的设备信息获取