左神算法进阶班1_5BFPRT算法
在无序数组中找到第k大的数
1)分组,每N个数一组,(一般5个一组)
2)每组分别进行排序,组间不排序
3)将每个组的中位数拿出来,若偶数,则拿上 / 下中位数, 成立一个一个新数组。
4)新数组递归调用BFPRT,则拿到整体的中位数num
5)以num来划分整体数组,小于在左,大于在右边,使用【荷兰国旗方法】
6)然后根据左右数组的规模,来确定进一步选择左右哪一部分;
7)然后选择好后,继续
一:背景介绍
在一大堆数中求其前k大或前k小的问题,简称TOP - K问题。而目前解决TOP - K问题最有效的算法即是BFPRT算法,其又称为中位数的中位数算法,该算法由Blum、Floyd、Pratt、Rivest、Tarjan提出,最坏时间复杂度为O(n)O(n)。
在首次接触TOP - K问题时,我们的第一反应就是可以先对所有数据进行一次排序,然后取其前k即可,但是这么做有两个问题:
(1):快速排序的平均复杂度为O(nlogn),但最坏时间复杂度为O(n2),不能始终保证较好的复杂度。
(2):我们只需要前k大的,而对其余不需要的数也进行了排序,浪费了大量排序时间。
除这种方法之外,堆排序也是一个比较好的选择,可以维护一个大小为k的堆,时间复杂度为O(nlogk)。
那是否还存在更有效的方法呢?受到快速排序的启发,通过修改快速排序中主元的选取方法可以降低快速排序在最坏情况下的时间复杂度(即BFPRT算法),并且我们的目的只是求出前k,故递归的规模变小,速度也随之提高。下面来简单回顾下快速排序的过程,以升序为例:
(1):选取主元(首元素,尾元素或一个随机元素);
(2):以选取的主元为分界点,把小于主元的放在左边,大于主元的放在右边;
(3):分别对左边和右边进行递归,重复上述过程。
二:BFPRT算法过程及代码
BFPRT算法步骤如下:
(1):选取主元;
(1.1):将n个元素划分为[n / 5]个组,每组5个元素,若有剩余,舍去;
(1.2):使用插入排序找到[n / 5]个组中每一组的中位数;
(1.3):对于(1.2)中找到的所有中位数,调用BFPRT算法求出它们的中位数,作为主元;
(2):以(1.3)选取的主元为分界点,把小于主元的放在左边,大于主元的放在右边;
(3):判断主元的位置与k的大小,有选择的对左边或右边递归。//即根据K的位置判断选择哪一部分继续迭代
上面的描述可能并不易理解,先看下面这幅图:
BFPRT()调用GetPivotIndex()和Partition()来求解第k小,在这过程中,GetPivotIndex()也调用了BFPRT(),即GetPivotIndex)和BFPRT()为互递归的关系。
下面为代码实现,其所求为前K小的数:
C++代码:
/******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 = ;
int array[] = { ,,,,,,-,,,- }; cout << "原数组:";
for (int i = ; i < ; i++)
cout << array[i] << " ";
cout << endl; cout << "第" << k << "小值为:" << array[BFPRT(array, , , k)] << endl; cout << "变换后的数组:";
for (int i = ; i < ; i++)
cout << array[i] << " ";
cout << endl; return ;
} /* 插入排序,返回中位数下标 */
int InsertSort(int array[], int left, int right)
{
int temp;
int j;
for (int i = left + ; i <= right; i++)
{
temp = array[i];
j = i - ;
while (j >= left && array[j] > temp)
array[j + ] = array[j--];
array[j + ] = temp;
} return ((right - left) >> ) + left;
} /* 返回中位数的中位数下标 */
int GetPivotIndex(int array[], int left, int right)
{
if (right - left < )
return InsertSort(array, left, right); int sub_right = left - ;
for (int i = left; i + <= right; i += )
{
int index = InsertSort(array, i, i + ); //找到五个元素的中位数的下标
swap(array[++sub_right], array[index]); //依次放在左侧
} return BFPRT(array, left, sub_right, ((sub_right - left + ) >> ) + );
} /* 利用中位数的中位数的下标进行划分,返回分界线下标 */
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 + ;
if (num == k)
return divide_index;
else if (num > k)
return BFPRT(array, left, divide_index - , k);
else
return BFPRT(array, divide_index + , right, k - num);
}
Java代码:
public class BFPRT {
//前k小
public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
if (k < 1 || k > arr.length) {
return arr;
}
int minKth = getMinKthByBFPRT(arr, k);
int[] res = new int[k];
int index = 0;
for (int i = 0; i != arr.length; i++) {
if (arr[i] < minKth) {
res[index++] = arr[i];
}
}
for (; index != res.length; index++) {
res[index] = minKth;
}
return res;
}
//第k小
public static int getMinKthByBFPRT(int[] arr, int K) {
int[] copyArr = copyArray(arr);
return select(copyArr, 0, copyArr.length - 1, K - 1);
}
public static int[] copyArray(int[] arr) {
int[] res = new int[arr.length];
for (int i = 0; i != res.length; i++) {
res[i] = arr[i];
}
return res;
}
//给定一个数组和范围,求第i小的数
public static int select(int[] arr, int begin, int end, int i) {
if (begin == end) {
return arr[begin];
}
int pivot = medianOfMedians(arr, begin, end);//划分值
int[] pivotRange = partition(arr, begin, end, pivot);
if (i >= pivotRange[0] && i <= pivotRange[1]) {
return arr[i];
}
else if (i < pivotRange[0]) {
return select(arr, begin, pivotRange[0] - 1, i);
}
else {
return select(arr, pivotRange[1] + 1, end, i);
}
}
//在begin end范围内进行操作
public static int medianOfMedians(int[] arr, int begin, int end) {
int num = end - begin + 1;
int offset = num % 5 == 0 ? 0 : 1;//最后一组的情况
int[] mArr = new int[num / 5 + offset];//中位数组成的数组
for (int i = 0; i < mArr.length; i++) {
int beginI = begin + i * 5;
int endI = beginI + 4;
mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
}
return select(mArr, 0, mArr.length - 1, mArr.length / 2);
//只不过i等于长度一半,用来求中位数
}
//经典partition过程
public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
int small = begin - 1;
int cur = begin;
int big = end + 1;
while (cur != big) {
if (arr[cur] < pivotValue) {
swap(arr, ++small, cur++);
}
else if (arr[cur] > pivotValue) {
swap(arr, cur, --big);
}
else {
cur++;
}
}
int[] range = new int[2];
range[0] = small + 1;
range[1] = big - 1;
return range;
}
//五个数排序,返回中位数
public static int getMedian(int[] arr, int begin, int end) {
insertionSort(arr, begin, end);
int sum = end + begin;
int mid = (sum / 2) + (sum % 2);
return arr[mid];
}
//手写排序
public static void insertionSort(int[] arr, int begin, int end) {
for (int i = begin + 1; i != end + 1; i++) {
for (int j = i; j != begin; j--) {
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
}
else {
break;
}
}
}
}
//交换值
public static void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
//打印
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
// sorted : { 1, 1, 1, 1, 2, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 7, 9, 9, 9 }
printArray(getMinKNumsByBFPRT(arr, 10));
}
}
左神算法进阶班1_5BFPRT算法的更多相关文章
- 左神算法进阶班1_4Manacher算法
#include <iostream> #include <string> using namespace std; //使用manacher算法寻找字符中最长的回文子串 in ...
- 左神算法进阶班1_1添加最少字符得到原字符N次
Problem: 给定一个字符串str1,只能往str1的后面添加字符变成str2. 要求1:str2必须包含两个str1,两个str1可以有重合,但是不能以同一个位置开头. 要求2:str2尽量短最 ...
- 左神算法进阶班3_1构造数组的MaxTree
题目 一个数组的MaxTree定义: 数组必须没有重复元素 MaxTree是一棵二叉树,数组的每一个值对应一个二叉树节点 包括MaxTree树在内且在其中的每一棵子树上,值最大的节点都是树的头 给定一 ...
- 左神算法进阶班8_1数组中累加和小于等于aim的最长子数组
[题目] 给定一个数组arr,全是正数:一个整数aim,求累加和小于等于aim的,最长子数组,要求额外空间复杂度O(1),时间复杂度O(N) [题解] 使用窗口: 双指针,当sum <= aim ...
- 左神算法进阶班6_1LFU缓存实现
[题目] LFU也是一个著名的缓存算法,自行了解之后实现LFU中的set 和 get 要求:两个方法的时间复杂度都为O(1) [题解] LFU算法与LRU算法很像 但LRU是最新使用的排在使用频率最前 ...
- 左神算法进阶班5_4设计可以变更的缓存结构(LRU)
[题目] 设计一种缓存结构,该结构在构造时确定大小,假设大小为K,并有两个功能: set(key, value):将记录(key, value)插入该结构. get(key):返回key对应的valu ...
- 左神算法进阶班4_2累加和为aim的最长子数组
[题目] 给定一个数组arr,和一个整数aim,求在arr中,累加和等于num的最长子数组的长度 例子: arr = { 7,3,2,1,1,7,7,7 } aim = 7 其中有很多的子数组累加和等 ...
- 左神算法基础班4_1&2实现二叉树的先序、中序、后序遍历,包括递归方式和非递归
Problem: 实现二叉树的先序.中序.后序遍历,包括递归方式和非递归方式 Solution: 切记递归规则: 先遍历根节点,然后是左孩子,右孩子, 根据不同的打印位置来确定中序.前序.后续遍历. ...
- 左神算法基础班5_1设计RandomPool结构
Problem: 设计RandomPool结构 [题目] 设计一种结构,在该结构中有如下三个功能: insert(key):将某个key加入到该结构,做到不重复加入. delete(key):将原本在 ...
随机推荐
- 通过js渲染高层级DOM实现网页加水印
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- NX二次开发-UFUN获取工程图详细信息UF_DRAW_ask_drawing_info
NX9+VS2012 #include <uf.h> #include <uf_draw.h> #include <uf_part.h> UF_initialize ...
- 剑指offer——07用两个栈实现队列
题目描述 用两个栈来实现一个队列,完成队列的Push和Pop操作. 队列中的元素为int类型. 题解: 有两个栈,stack1和stack2 push只在stack1上操作,pop只在stack2 ...
- Mysql命令增加、修改、删除表字段
alter add 命令用来增加表的字段: alter add命令格式:alter table 表名 add字段 类型 其他:如下所示: ) comment '单位' alter drop 命令删除表 ...
- Vue的项目搭建及请求生命周期
目录 Vue的项目搭建及请求生命周期 Vue-CLI的项目搭建 环境搭建 项目创建 pycharm运行Vue项目 Vue项目的大体结构 Vue的请求生命周期 两个小用法 Vue的项目搭建及请求生命周期 ...
- Jupyter的使用复习
Jupyter的使用 esc+m 切换到markdown模式 shift+enter 运行 a 向上新增代码块 b 向下新增代码块 y python代码模式 file-->download as ...
- 使用neo4j图数据库的import工具导入数据 -方法和注意事项
背景 最近我在尝试存储知识图谱的过程中,接触到了Neo4j图数据库,这里我摘取了一段Neo4j的简介: Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中.它是一个嵌 ...
- nodejs http ejs
// ejs-demo.jsvar http = require('http'); var ejs = require('ejs'); var url = require('url'); // 搭建 ...
- Jinja2模板引擎
这里是Jinja2通用模板语言的文档. Jinja2 在其是一个 Python 2.4 库之前,被设计 为是灵活.快速和安全的.如果你接触过其它的基于文本的模板语言,比如 Smarty 或 Djang ...
- The linux command 之存储媒介
一.常用的命令 mount:挂载文件系统 unmount:卸载文件系统 fdisk:硬盘分区命令 fdformat:格式化软盘 fsck:检查和修复文件系统 mkfs:创建文件系统 dd:转换和拷贝一 ...