先说快排最坏情况下的时间复杂度为n^2。

正常情况:

 

最坏的情况下,待排序的记录序列正序或逆序,每次划分只能得到一个比上一次划分少一个记录的子序列,(另一个子序列为空)。此时,必须经过n-1次递归调用才能把所有记录定位,而且第i趟划分需要经过n-i次比较才能找个才能找到第i个记录的位置,因此时间复杂度为

 
所以BFPRT本质上是在寻找正确的pivot元素!!!避免这种最坏情况出现。
 
 

在BFPTR算法中,仅仅是改变了快速排序Partion中的pivot值的选取,在快速排序中,我们始终选择第一个元素或者最后一个元素作为pivot,而在BFPTR算法中,每次选择五分中位数的中位数作为pivot,这样做的目的就是使得划分比较合理,从而避免了最坏情况的发生。算法步骤如下:

1. 将  个元素划为  组,每组5个,至多只有一组由  个元素组成。 
2. 寻找这  个组中每一个组的中位数,这个过程可以用插入排序。 
3. 对步骤2中的  个中位数,重复步骤1和步骤2,递归下去,直到剩下一个数字。
4. 最终剩下的数字即为pivot,把大于它的数全放左边,小于等于它的数全放右边。 
5. 判断pivot的位置与k的大小,有选择的对左边或右边递归。

当你看到本质上后,至于”首先把数组按5个数为一组进行分组,最后不足5个的忽略。 ” 、“偶数个元素的中位数取中间2个中较小的一个。”这些小细节都是无关紧要的了!!!

下面为代码实现,其所求为前 k 小的数

#include <iostream>
#include <algorithm> using namespace std; int InsertSort(int array[], int left, int right);
int GetPivotIndex(int array[], int left, int right);
int Partition(int array[], int left, int right, int pivot_index);
int BFPRT(int array[], int left, int right, int k); int main()
{
int k = 8; // 1 <= k <= array.size
int array[20] = { 11,9,10,1,13,8,15,0,16,2,17,5,14,3,6,18,12,7,19,4 }; cout << "原数组:";
for (int i = 0; i < 20; i++)
cout << array[i] << " ";
cout << endl; // 因为是以 k 为划分,所以还可以求出第 k 小值
cout << "第 " << k << " 小值为:" << array[BFPRT(array, 0, 19, k)] << endl; cout << "变换后的数组:";
for (int i = 0; i < 20; i++)
cout << array[i] << " ";
cout << endl; return 0;
} /**
* 对数组 array[left, right] 进行插入排序,并返回 [left, right]
* 的中位数。
*/
int InsertSort(int array[], int left, int right)
{
int temp;
int j; for (int i = left + 1; i <= right; i++)
{
temp = array[i];
j = i - 1;
while (j >= left && array[j] > temp)
array[j + 1] = array[j--];
array[j + 1] = temp;
} return ((right - left) >> 1) + left;
} /**
* 数组 array[left, right] 每五个元素作为一组,并计算每组的中位数,
* 最后返回这些中位数的中位数下标(即主元下标)。
*
* @attention 末尾返回语句最后一个参数多加一个 1 的作用其实就是向上取整的意思,
* 这样可以始终保持 k 大于 0。
*/
int GetPivotIndex(int array[], int left, int right)
{
if (right - left < 5)
return InsertSort(array, left, right); int sub_right = left - 1; // 每五个作为一组,求出中位数,并把这些中位数全部依次移动到数组左边
for (int i = left; i + 4 <= right; i += 5)
{
int index = InsertSort(array, i, i + 4);
swap(array[++sub_right], array[index]);
} // 利用 BFPRT 得到这些中位数的中位数下标(即主元下标)
return BFPRT(array, left, sub_right, ((sub_right - left + 1) >> 1) + 1);
} /**
* 利用主元下标 pivot_index 进行对数组 array[left, right] 划分,并返回
* 划分后的分界线下标。
*/
int Partition(int array[], int left, int right, int pivot_index)
{
swap(array[pivot_index], array[right]); // 把主元放置于末尾 int partition_index = left; // 跟踪划分的分界线
for (int i = left; i < right; i++)
{
if (array[i] < array[right])
{
swap(array[partition_index++], array[i]); // 比主元小的都放在左侧
}
} swap(array[partition_index], array[right]); // 最后把主元换回来 return partition_index;
} /**
* 返回数组 array[left, right] 的第 k 小数的下标
*/
int BFPRT(int array[], int left, int right, int k)
{
int pivot_index = GetPivotIndex(array, left, right); // 得到中位数的中位数下标(即主元下标)
int partition_index = Partition(array, left, right, pivot_index); // 进行划分,返回划分边界
int num = partition_index - left + 1; if (num == k)
return partition_index;
else if (num > k)
return BFPRT(array, left, partition_index - 1, k);
else
return BFPRT(array, partition_index + 1, right, k - num);
}

  

运行如下:

原数组:11 9 10 1 13 8 15 0 16 2 17 5 14 3 6 18 12 7 19 4
第 8 小值为:7
变换后的数组:4 0 1 3 2 5 6 7 8 9 10 12 13 14 17 15 16 11 18 19

性能分析:

划分时以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个数。

时间复杂度为O(n)的原因

我们选取的x可以在每次递归的时候最少淘汰(n/10-2)x3的数据量

详细的时间复杂度分析见:https://blog.csdn.net/LaoJiu_/article/details/54986553

BFPRT 算法 (TOP-K 问题)——本质就是在利用分组中位数的中位数来找到较快排更合适的pivot元素的更多相关文章

  1. 经典算法 BFPRT算法详解

    内容: 1.原始问题     =>  O(N*logN) 2.BFPRT算法    => O(N) 1.原始问题 问题描述:给你一个整型数组,返回其中第K小的数 普通解法: 这道题可以利用 ...

  2. 2018.4.24 快排查找第K大

    import java.util.Arrays; /* 核心思想:利用快排思想,先假定从大到小排序,找枢纽,枢纽会把大小分开它的两边,当枢纽下标等于k时, 即分了k位在它左边或右边,也就是最大或最小的 ...

  3. Top K问题-BFPRT算法、Parition算法

    BFPRT算法原理 在BFPTR算法中,仅仅是改变了快速排序Partion中的pivot值的选取,在快速排序中,我们始终选择第一个元素或者最后一个元素作为pivot,而在BFPTR算法中,每次选择五分 ...

  4. 程序员编程艺术:第三章续、Top K算法问题的实现

    程序员编程艺术:第三章续.Top K算法问题的实现 作者:July,zhouzhenren,yansha.     致谢:微软100题实现组,狂想曲创作组.     时间:2011年05月08日    ...

  5. LC T668笔记 & 有关二分查找、第K小数、BFPRT算法

    LC T668笔记 [涉及知识:二分查找.第K小数.BFPRT算法] [以下内容仅为本人在做题学习中的所感所想,本人水平有限目前尚处学习阶段,如有错误及不妥之处还请各位大佬指正,请谅解,谢谢!] !! ...

  6. Top k问题(线性时间选择算法)

    问题描述:给定n个整数,求其中第k小的数. 分析:显然,对所有的数据进行排序,即很容易找到第k小的数.但是排序的时间复杂度较高,很难达到线性时间,哈希排序可以实现,但是需要另外的辅助空间. 这里我提供 ...

  7. 使用堆实现Top K 算法 JS 实现

    1. 堆算法Top,时间复杂度 O(LogN) function top(arr,comp){ if(arr.length == 0){return ;} var i = arr.length / 2 ...

  8. 查找第K小的数 BFPRT算法

    出处 http://blog.csdn.net/adong76/article/details/10071297 BFPRT算法是解决从n个数中选择第k大或第k小的数这个经典问题的著名算法,但很多人并 ...

  9. 算法进阶面试题02——BFPRT算法、找出最大/小的K个数、双向队列、生成窗口最大值数组、最大值减最小值小于或等于num的子数组数量、介绍单调栈结构(找出临近的最大数)

    第二课主要介绍第一课余下的BFPRT算法和第二课部分内容 1.BFPRT算法详解与应用 找到第K小或者第K大的数. 普通做法:先通过堆排序然后取,是n*logn的代价. // O(N*logK) pu ...

随机推荐

  1. Spring框架学习

    没有状态变化的对象(无状态对象):应当做成单例. Spring-framework的下载:http://repo.spring.io/release/org/springframework/sprin ...

  2. 转youhu科技的文章 勿怪 感激 感激

    资源加载 资源加载是加载模块中最为耗时的部分,其CPU开销在Unity引擎中主要体现在Loading.UpdatePreloading和Loading.ReadObject两项中,相信经常查看Prof ...

  3. hdu 3836 Equivalent Sets trajan缩点

    Equivalent Sets Time Limit: 12000/4000 MS (Java/Others)    Memory Limit: 104857/104857 K (Java/Other ...

  4. StringBuffer 清空StringBuffer的实例的三种方法

    @Test public void testStringbuffer(){ //StringBuffer类没有clear方法,不过可以通过下面两种方法来清空一个StringBuffer的实例: Str ...

  5. AjaxHandler

    概要 AjaxHandler组件是在ASP.NET MVC Web应用程序中实现ajax功能的一系列扩展方法,该组件的最初的实现方法借鉴了网上流行的部分源代码, ,经过博主不断完善和改进后推出的比较成 ...

  6. vs2015 VS-Visual Studio-IIS Express 支持局域网访问

    使用Visual Studio开发Web网页的时候有这样的情况:想要在调试模式下让局域网的其他设备进行访问,以便进行测试.虽然可以部署到服务器中,但是却无法进行调试,就算是注入进程进行调试也是无法达到 ...

  7. L1-047. 装睡

    水题直接上代码: #include<iostream> #include<algorithm> using namespace std; int main() { int n; ...

  8. PowerDesign的简单使用方法

    PowerDesigner是一款功能非常强大的建模工具软件,足以与Rose比肩,同样是当今最著名的建模软件之一.Rose是专攻UML对象模型的建模工具,之后才向数据库建模发展,而PowerDesign ...

  9. Android 用虹软SDK做人脸识别

    人脸识别第三方sdk比较多,但是大多都是收费的或者限制次数什么的,虹软的效果还不错,全免费也不需要联网 V1.2版本使用和快速集成:https://www.jianshu.com/p/8dee89ec ...

  10. 00-python-内置函数笔记

    01.enumerate()函数用于将一个可遍历的数据对象(如 列表.元组或字符串)组合为一个索引序列,同时列出数据和数据包下标,一般用在for循环中 for i, element in enumer ...