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

各位小伙伴,由于本篇文章代码太过杂乱。我于 2018年12月25日 对文中介绍的算法进行了重写。点击上面的蓝色字体,可以阅读重写后的文章,修复了一些存在的错误。


最容易想到的算法是采用一种排序算法先将数组按不降的次序排好,然后从排好序的数组中捡出第k个元素。这样的算法在最坏情况下时间复杂度是O(nlog2^n)。

实际上,我们可以设计出在最坏情况下的时间复杂度为O(n)的算法。

利用分治算法并结合快排思想,很容易达到O(n)的时间复杂度。其核心思想在于快排中基准的选取。(根据严蔚敏版教材,一般直接选取第一个元素作为快排基准。但求第k小元素,则依赖于一种中值选取法,以加速剪枝)。

阅读以下内容时,需要先学习快排算法,可以看看这篇文章《[Data Structure]九大内部排序算法》。

下面举个例子,如何达到O(n)选取第k小的元素。

问题:如何在O(n)内,确定A[17]中第k小的元素? A[17] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

这里给出一个分治算法,选取基准的求解过程。

step1 设置一个值r,将A[15]分为长度为r的几个组。(假设r = 5)

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

G1               G2                   G3                  G4

分组结果,G1[5] = {0, 1, 2, 3, 4} ;G2[5] = {5, 6, 7, 8, 9};G3[5] = {10, 11, 12, 13, 14};G4[2] = {15, 16}。这里注意第四组,只有2个元素。

step2 分别求取G1~4组别中的中值,G1 = 2, G2 = 7, G3 = 12, G4 = 15。

并由这四个数再组成一个数组G[4] = {2, 7, 12, 15}。

step3 求得G[4] = {2, 7, 12, 15}数组中的中值,mid = 7。

那么,mid = 7便是最终选取出来的快排划分基准。

以下程序中,partition函数(第13行)以上部分即为上述基准选取过程。这里需要说明的是,求得中值后,程序中并没有开辟额外空间,而是在原有A[]基础上进行操作的,将中值放在最前面,故需要swap以保证数据信息不丢失。

//A[low..high]
int select_rank_k(int A[], int low, int high, int k)
{int r_group = ceil((high - low + 1)*1.0 / r);//ceil取上限,总共分为r_group个组//计算每个分组中值,存于A[]最前面for (int i = 1; i <= r_group; ++i) {sort(&A[low + (i - 1)*r], &A[(low + i*r - 1) > high ? high : (low + i*r - 1)]);swap(A[low + i - 1], A[low + (i-1)*r + r / 2]);}//获得每个组的中值的中值(并置于A[low]位置,方便调用快排划分函数)sort(&A[low], &A[low + r_group]);swap(A[low], A[r_group / 2]);int cur = partition(A, low, high);if (cur == k-1){return A[cur];}else if (cur < k){return select_rank_k(A, cur + 1, high, k);}else{return select_rank_k(A, low, cur - 1, k);}
}

2018年6月4日修正

swap(A[low], A[r_group / 2]); 应该更改为swap(A[low], A[low+r_group / 2]);

程序中其它执行步的时间复杂度都至多是n的倍数。如果用T(n)表示算法在数组长度为n的时间复杂度,则当n≥24时,有递归关系

其中c是常数。从上述递推关系式出发,用数学归纳法可以证明,

所以,在最坏情况下,select_rank_k算法的时间复杂度是O(n)。

最后,对整个问题抽象以下,并给出完整DEMO。问题:已知n元数组A[1..n],试确定其中第k小的元素。

补充:36行需修正为swap(A[low], A[low+r_group / 2]);

#include <stdio.h>
#include <algorithm>
#include <math.h>
using namespace std;//划分——每次划分唯一确定一个元素位置
int partition(int A[], int low, int high)
{int pivot = A[low];    //一般采用严蔚敏教材版本,以第1个位置为基准while (low < high){while (low < high && A[high] >= pivot){--high;}A[low] = A[high];  //将比基准小的元素移动到左端while (low < high && A[low] <= pivot){++low;}A[high] = A[low];  //将比基准小的元素移动到右端}A[low] = pivot;return low;
}int r = 5;
//A[low..high]
int select_rank_k(int A[], int low, int high, int k)
{int r_group = ceil((high - low + 1)*1.0 / r);//ceil取上限,总共分为r_group个组//计算每个分组中值,存于A[]最前面for (int i = 1; i <= r_group; ++i) {sort(&A[low + (i - 1)*r], &A[(low + i*r - 1) > high ? high : (low + i*r - 1)]);swap(A[low + i - 1], A[low + (i-1)*r + r / 2]);}//获得每个组的中值的中值(并置于A[low]位置,方便调用快排划分函数)sort(&A[low], &A[low + r_group]);swap(A[low], A[r_group / 2]);int cur = partition(A, low, high);if (cur == k-1){return A[cur];}else if (cur < k){return select_rank_k(A, cur + 1, high, k);}else{return select_rank_k(A, low, cur - 1, k);}
}int main(void)
{int A[15] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 };printf("%d\n", select_rank_k(A, 0, 3, 2));return 0;
}

@qingdujun

2017-11-22 北京 怀柔

Reference:陈玉福.计算机算法设计与分析,59-61

分治算法 求第k小元素 O(n) O(nlog2^n)相关推荐

  1. 求第k小元素:采用特定分治策略

    问题[描述算法问题,首选形式化方式(数学语言),其次才是非形式化方式(日常语言)]设L是n个元素的集合,从L中选取第k小的元素,其中1<=k<=n.这里的第k小元素是指,当L按从小到大排好 ...

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

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

  3. 算法-寻找第k小元素(C)

    序言 刚开始我认为,寻找第k小的元素:简单呀,先对所有元素排序,之后再找不就完事啦,这时时间复杂度在O(nlgn).那有没有更好的排序的方法了呢?答案:当然是有的. 算法基本思路: (1) 当规模小于 ...

  4. Hoare选择算法 寻找第k小元素C实现 算法的“AWK脚手架和grap运行过程分析”

    现实生活中常有找"最大"."最小"及"中位数"等需求,解决这样的问题不用将整个序列排序.寻找"最大"."最小& ...

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

    我自己搭建了博客,以后可能不太在CSDN上发博文了,https://www.qingdujun.com/ . 去年写了一篇<分治算法 求第kkk小元素 O(n)O(n)O(n) & O( ...

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

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

  7. 求数列中的第1~k小元素

    1.问题描述 设计算法实现在一个具有在n各互不相同元素的数组A[1-n]中找出所有前k个最小元素的问题,这里k不是常量,即它是输入数据的一部分.要求算法的时间复杂性为Θ(n). 2. 具体要求 输入的 ...

  8. 分治算法求n个元素的最大值和最小值

    分治算法求n个元素的最大值和最小值 算法思想:         1.将n个数均分为s1和s2         2.分别求解s1和s2的最大值和最小值            s1最大值为max1,s1最 ...

  9. 解决寻找第K小元素问题——三种不同的算法实现

    个人原创,禁止转载--Zetrue_Li 问题描述:在一个序列里找出第K小元素 以下程序基于函数 int select_kth_smallest(list q, int k) 实现 :返回向量q中第k ...

最新文章

  1. mxnet 配置gpu
  2. PHP设计模式之装饰者模式
  3. python找零钱问题_Python基于回溯法子集树模板解决找零问题示例
  4. tinycore php,tinycore中文支持
  5. 使用jdbc执行SQL实现登录查询1-带配置文件和工具类
  6. 带你自学Python系列(八):列表具体操作思维导图总结,再也不用翻书查用法了!...
  7. 风格迁移与Gram matrix
  8. User Profile的名称和显示名称
  9. 合金理论自动化工具包(Alloy-Theoretic Automated Toolkit:ATAT)的安装
  10. 3dmax如何使用模型快速切片命令
  11. 搜狗输入法 VS 拼音加加
  12. 剖析Android shape标签的绘制
  13. [置顶] 礼物:《红孩儿引擎内功心法修练与Cocos2d-x》之结点系统(场景,层,精灵)...
  14. Asp.Net 密码加密技术
  15. 《操作系统真象还原》第十四章 ---- 实现文件系统 任务繁多 饭得一口口吃路得一步步走啊(上一)
  16. 计算机图形学与虚拟环境pdf,计算机图形学与虚拟现实环境.ppt
  17. 软考java题目_软件工程软考题目总结
  18. 睡眠排序算法c语言实现,Linux 进程必知必会
  19. android studio try catch自动生成,Android Studio:Try-catch异常崩溃了应用程序
  20. 揭秘小程序上线不到一周,每天2万销售额,究竟怎么做到的?

热门文章

  1. 新浪微博开发平台试用
  2. 零基础学习Java编程培训需要了解哪些知识
  3. Android中wifi认证的实现
  4. geogebra画一段椭圆
  5. 使用 Docker 运行微信 PC 客户端
  6. 如何配置阿里云安全组授权对象IP段
  7. linux springboot开机启动,SpringBoot 部署到Linux开机自启动和运行
  8. Mac安装虚拟机Parallels Desktop,以及Windows10系统详细教程
  9. php ci CodeIgniter框架图片缩略图处理类优化 缩放到固定大小
  10. Eclipse修改项目编码方式