首页 > 编程语言 >BFPRT算法(TOP-K问题)

BFPRT算法(TOP-K问题)

时间:2022-12-06 19:31:56浏览次数:55  
标签:right int TOP 中位数 算法 BFPRT array left


一:背景介绍

在一大堆数中求其前k大或前k小的问题,简称TOP-K问题。而目前解决TOP-K问题最有效的算法即是BFPRT算法,其又称为中位数的中位数算法,该算法由Blum、Floyd、Pratt、Rivest、Tarjan提出,最坏时间复杂度为BFPRT算法(TOP-K问题)_c++

在首次接触TOP-K问题时,我们的第一反应就是可以先对所有数据进行一次排序,然后取其前k即可,但是这么做有两个问题:
(1):快速排序的平均复杂度为BFPRT算法(TOP-K问题)_算法_02,但最坏时间复杂度为BFPRT算法(TOP-K问题)_TOP-K_03,不能始终保证较好的复杂度。
(2):我们只需要前k大的,而对其余不需要的数也进行了排序,浪费了大量排序时间。

除这种方法之外,堆排序也是一个比较好的选择,可以维护一个大小为k的堆,时间复杂度为BFPRT算法(TOP-K问题)_TOP-K_04

那是否还存在更有效的方法呢?受到快速排序的启发,通过修改快速排序中主元的选取方法可以降低快速排序在最坏情况下的时间复杂度(即BFPRT算法),并且我们的目的只是求出前k,故递归的规模变小,速度也随之提高。下面来简单回顾下快速排序的过程,以升序为例:
(1):选取主元(首元素,尾元素或一个随机元素);
(2):以选取的主元为分界点,把小于主元的放在左边,大于主元的放在右边;
(3):分别对左边和右边进行递归,重复上述过程。

二:BFPRT算法过程及代码

BFPRT算法步骤如下:
(1):选取主元;
  (1.1):将n个元素划分为BFPRT算法(TOP-K问题)_TOP-K_05个组,每组5个元素,若有剩余,舍去;
  (1.2):使用插入排序找到BFPRT算法(TOP-K问题)_TOP-K_05个组中每一组的中位数;
  (1.3):对于(1.2)中找到的所有中位数,调用BFPRT算法求出它们的中位数,作为主元;
(2):以(1.3)选取的主元为分界点,把小于主元的放在左边,大于主元的放在右边;
(3):判断主元的位置与k的大小,有选择的对左边或右边递归。

上面的描述可能并不易理解,先看下面这幅图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVeP1uIY-1584174897186)(http://oi0fekpsr.bkt.clouddn.com/BFPRT1.png)]

BFPRT()调用GetPivotIndex()和Partition()来求解第k小,在这过程中,GetPivotIndex()也调用了BFPRT(),即GetPivotIndex)和BFPRT()为互递归的关系。

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

/**
* BFPRT算法(前K小数问题)
*
* author 刘毅(Limer)
* date 2017-01-25
* mode C++
*/

#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, const int & k); //求第k小,返回其位置的下标

int main()
{
int k = 5;
int array[10] = { 1,1,2,3,1,5,-1,7,8,-10 };

cout << "原数组:";
for (int i = 0; i < 10; i++)
cout << array[i] << " ";
cout << endl;

cout << "第" << k << "小值为:" << array[BFPRT(array, 0, 9, k)] << endl;

cout << "变换后的数组:";
for (int i = 0; i < 10; i++)
cout << array[i] << " ";
cout << endl;

return 0;
}

/* 插入排序,返回中位数下标 */
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;
}

/* 返回中位数的中位数下标 */
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]); //依次放在左侧
}

return BFPRT(array, left, sub_right, ((sub_right - left + 1) >> 1) + 1);
}

/* 利用中位数的中位数的下标进行划分,返回分界线下标 */
int Partition(int array[], int left, int right, int pivot_index)
{
swap(array[pivot_index], array[right]); //把基准放置于末尾

int divide_index = left; //跟踪划分的分界线
for (int i = left; i < right; i++)
{
if (array[i] < array[right])
swap(array[divide_index++], array[i]); //比基准小的都放在左侧
}

swap(array[divide_index], array[right]); //最后把基准换回来
return divide_index;
}

int BFPRT(int array[], int left, int right, const int & k)
{
int pivot_index = GetPivotIndex(array, left, right); //得到中位数的中位数下标
int divide_index = Partition(array, left, right, pivot_index); //进行划分,返回划分边界
int num = divide_index - left + 1;
if (num == k)
return divide_index;
else if (num > k)
return BFPRT(array, left, divide_index - 1, k);
else
return BFPRT(array, divide_index + 1, right, k - num);
}

三:时间复杂度分析

BFPRT算法在最坏情况下的时间复杂度是BFPRT算法(TOP-K问题)_c++,下面予以证明。令BFPRT算法(TOP-K问题)_中位数_08为所求的时间复杂度,则有:
BFPRT算法(TOP-K问题)_算法_09
其中:

  • BFPRT算法(TOP-K问题)_c++_10来自GetPivotIndex(),n个元素,5个一组,共有BFPRT算法(TOP-K问题)_BFPRT_11个中位数;
  • BFPRT算法(TOP-K问题)_BFPRT_12来自BFPRT(),在BFPRT算法(TOP-K问题)_BFPRT_11个中位数中,主元x大于其中 BFPRT算法(TOP-K问题)_中位数_14的中位数,而每个中位数在其本来的5个数的小组中又大于或等于其中的3个数,所以主元x至少大于所有数中的BFPRT算法(TOP-K问题)_BFPRT_15个。即划分之后,任意一边的长度至少为BFPRT算法(TOP-K问题)_TOP-K_16,在最坏情况下,每次选择都选到了BFPRT算法(TOP-K问题)_算法_17的那一部分。
  • BFPRT算法(TOP-K问题)_c++_18来自其它操作,比如InsertSort(),以及GetPivotIndex()和Partition()里所需的一些额外操作。

BFPRT算法(TOP-K问题)_TOP-K_19,其中t为未知,它可以是一个正常数,也可以是一个关于n的函数,代入上式:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ t⋅n&≤\frac {t⋅…
其中c为一个正常数,故t也是一个正常数,即BFPRT算法(TOP-K问题)_TOP-K_20,因此BFPRT算法(TOP-K问题)_中位数_21,至此证明结束。

接下来的更有意思的话题就是BFPRT算法为何选5作为分组基准,为何不是2,3,7,9呢?
首先排除偶数,对于偶数我们很难取舍其中位数,而奇数很容易。
再者对于3而言,会有BFPRT算法(TOP-K问题)_算法_22,它本身还是操作了n个元素,与以5为基准的BFPRT算法(TOP-K问题)_算法_23相比,其复杂度并没有减少。
对于,7,9,…而言,对于上式中的10c,其整体都会增加,所以与5相比,5更适合。

参考文献:
[1] 算法导论(第3版)
[2] 算法设计与分析基础(第3版)
[3] Wikipedia. ​​​Median of medians​​​ [4] ACdreamers. BFPRT 算法
[5] NOALGO. ​​BFPRT算法​


标签:right,int,TOP,中位数,算法,BFPRT,array,left
From: https://blog.51cto.com/u_11937443/5916569

相关文章

  • 算法复杂度分析概要
    一:渐近符号1.1符号的辨析1.1.1符号OO,读作“大O”,非正式来说,O(g(n))是增长次数小于等于g(n)及其常数倍(n趋向于无穷大)的函数集合。  定义如果函数f(n)包含在O(g(n))中,......
  • 面试算法题
    小球高处落下回弹运动距离"""一个小球从100m高度落下,每次弹回原高度一半.计算:--总共弹起多少次?(最小弹起高度0.01m)13次--全过程总共移动......
  • Vue的keep-alive、虚拟DOM的作用、diff算法
    一、keep-alive作用:keep-alive标签是vue的内置标签,可在组件切换过程中将状态保留在内存中,防止DOM重复渲染。标签属性:include:符合条件的组件会被缓存,exclude:符合条件的组......
  • 正确停止expdp导出任务使用stop_job
    ctr+c进入expdp交互界面输入stop_job=immediateselectjob_name,statefromdba_datapump_jobs;会发现任务已经变为notrunningexpdpnc65/nc65attach=SYS_EXPORT_F......
  • 朴素贝叶斯算法
    一,朴素贝叶斯算法理论基础朴素贝叶斯算法是基于贝叶斯定理与特征条件独立假设的分类方法。对于给定的训练集,首先基于特征条件独立假设学习输入输出的联合概率分布(朴素贝叶......
  • 每日算法之二叉树中和为某一值的路径(二)
    JZ34二叉树中和为某一值的路径(二)描述输入一颗二叉树的根节点root和一个整数expectNumber,找出二叉树中结点值的和为expectNumber的所有路径。1.该题路径定义为从树的......
  • Go-09 Go语言中数组、切片的排序算法以及sort包
    packagemainimport( "fmt" "sort")//Golang数组中的切片及sort包funcmain(){ //1.选择排序 varnumSlice=[]int{9,8,7,6,5,4} fori:=0;i<le......
  • iTOP3588开发板编译Android内核方法一
    iTOP3588开发板编译​​Android​​内核方法一:在Android源码目录下执行如下命令编译Android内核:./build.sh-CKA编译完成后如下图所示:编译后会在rockdev/Image-rk358......
  • iTOP3588开发板编译Android内核方法一
    iTOP3588开发板编译Android内核方法一:在Android源码目录下执行如下命令编译Android内核:./build.sh-CKA编译完成后如下图所示:编译后会在rockdev/Image-rk3588_s......
  • 字节二面,居然让我写一个 LFU 缓存策略算法,懵了!
    LRU全称"LeastRecentlyUsed",最近最少使用策略,判断最近被使用的时间,距离目前最远的数据优先被淘汰,作为一种根据访问时间来更改链表顺序从而实现缓存淘汰的算法,它是redis......