这里自己实现了一下BFPRT算法,并与别人的实现版本进行效率对比,以及与C++标准库中的sort排序后选取top-k进行效率对比。发现,C语言版本的效率更高一些,在数据量不是海量数据时,sort的速度竟然比BFPRT要快。
原因猜想:O(nlgn),就算是n=50000000,lgn也才25.5754,而bfprt的常数因子要比这个大,再加上stl做了大量的优化,速度上就更快了。
//
// BFPRT.cpp
// NowCoder
//
// Created by soybeanmilk on 2017/7/22.
// Copyright © 2017年 soybeanmilk. All rights reserved.
//
#include <iostream>
#include <vector>
#include <ctime>
#include <cstdlib>
using namespace std;
void insertsort(vector<int> &vi, int b, int e){//包含e
for(int i=b+1;i<=e;++i){
int temp=vi[i];
int j=i-1;
for(;j>=b && vi[j]>temp;--j){
vi[j+1]=vi[j];
}
vi[j+1]=temp;
}
}
int getMidIndex(vector<int> &vi, int b, int e){//包含e
//找中位数,最后返回找到的元素的下标
if(b==e) return b;
int i=b;
int index=0;
for(;i<=e-4;i+=5){//e-(e-4)+1=5
insertsort(vi, i, i+4);
//vi[b+index]=vi[i+2];//这个会使数组乱掉!
swap(vi[b+index],vi[i+2]);//应该是交换元素,而不仅仅是将中位数移动到一块。
++index;
}
if(i<=e){//剩余的不足5个的元素
insertsort(vi,i,e);
//vi[b+index]=vi[i+(e-i)/2];//这个会使数组乱掉!
swap(vi[b+index],vi[i+(e-i)/2]);//i~e可能为偶数个数,取中间两位低的一位为是位数,跟二分查找中取低位一样。
++index;//与上面的情况保持一致,index表示中位数的个数
}
return getMidIndex(vi, b, b+index-1);//继续找中位数的中位数,最后返回找到的元素的下标
}
int partition(vector<int> &vi, int b, int e, int val){//包含e
while(b<e){
while(b<e && vi[e]>val) --e;
if(b<e) vi[b]=vi[e];
while(b<e && vi[b]<=val) ++b;
if(b<e) vi[e]=vi[b];
}
vi[b]=val;
return b;
}
int BFPRT(vector<int> &vi, int b, int e, int k){//包含e,这里在调用BFPRT时,需要先对k做一下转换,
//找前10个最小的,那么这里传的下k为9,因为下标从0开始,0~9正好是10个数。
if(k<b || k>e)//表示k不符合规则
return -1;
int midindex=getMidIndex(vi, b, e);
int realindex=partition(vi, b, e, vi[midindex]);
if(realindex==k) return realindex;//返回此realindex,在此之前(包含此下标)的元素即为要找的全部元素。
//这个函数需要返回值,主要是为了返回-1这个错误情况。
if(realindex<k) return BFPRT(vi, realindex+1, e, k);//其实就是一直找下标k
else return BFPRT(vi, b, realindex-1, k);//其实就是一直找下标k
}
double random(double start, double end)
{
return start+(end-start)*rand()/(RAND_MAX + 1.0);
}
//别人的实现:http://blog.csdn.net/acdreamers/article/details/44656295
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <algorithm>
using namespace std;
const int N = 100000005;
int a[N];
//插入排序
void InsertSort(int a[], int l, int r)
{
for(int i = l + 1; i <= r; i++)
{
if(a[i - 1] > a[i])
{
int t = a[i];
int j = i;
while(j > l && a[j - 1] > t)
{
a[j] = a[j - 1];
j--;
}
a[j] = t;
}
}
}
//寻找中位数的中位数
int FindMid(int a[], int l, int r)
{
if(l == r) return a[l];
int i = 0;
int n = 0;
for(i = l; i < r - 5; i += 5)
{
InsertSort(a, i, i + 4);
n = i - l;
swap(a[l + n / 5], a[i + 2]);
}
//处理剩余元素
int num = r - i + 1;
if(num > 0)
{
InsertSort(a, i, i + num - 1);
n = i - l;
swap(a[l + n / 5], a[i + num / 2]);
}
n /= 5;
if(n == l) return a[l];
return FindMid(a, l, l + n);
}
//寻找中位数的所在位置
int FindId(int a[], int l, int r, int num)
{
for(int i = l; i <= r; i++)
if(a[i] == num)
return i;
return -1;
}
//进行划分过程
int Partion(int a[], int l, int r, int p)
{
swap(a[p], a[l]);
int i = l;
int j = r;
int pivot = a[l];
while(i < j)
{
while(a[j] >= pivot && i < j)
j--;
a[i] = a[j];
while(a[i] <= pivot && i < j)
i++;
a[j] = a[i];
}
a[i] = pivot;
return i;
}
int BFPTR_other(int a[], int l, int r, int k)
{
int num = FindMid(a, l, r); //寻找中位数的中位数
int p = FindId(a, l, r, num); //找到中位数的中位数对应的id
int i = Partion(a, l, r, p);
int m = i - l + 1;
if(m == k) return a[i];
if(m > k) return BFPTR_other(a, l, i - 1, k);
return BFPTR_other(a, i + 1, r, k - m);
}
int main(){
//生成测试数据
vector<int> vi;
srand(unsigned(time(0)));
int nums=50000000;
for(int i=0;i<nums;i++)
vi.push_back(int(random(0., 10.)*1000));
vector<int> vitp(vi);//备份测试数据,给sort函数使用
int k=666%nums;//随机取k值
cout<<nums<<" "<<k<<endl;
//我的实现
clock_t start=clock();
cout<<"my BFPRT: "<<endl;
BFPRT(vi, 0, (int)vi.size()-1, k);//注意这里(int)vi.size()-1
clock_t end=clock();
cout<<"used time: "<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<endl;
sort(vi.begin(),vi.begin()+k);//为了与下面的直接排序进行结果对比,所以排序一下。
//别人的实现
cout<<"other BFPRT: "<<endl;
for(int i=0;i<nums;i++)
a[i]=vitp[i];
start=clock();
BFPTR_other(a, 0, nums - 1, k);
end=clock();
cout<<"used time: "<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<endl;
//直接使用sort排序再选top-k
cout<<"direct sorted and select top-k: "<<endl;
start=clock();
sort(vitp.begin(),vitp.end());//不使用vi,而使用vitp,因为vi经过BFPRT后,大部分都是有序的了
end=clock();
cout<<"used time: "<<(double)(end-start)/CLOCKS_PER_SEC<<"s"<<endl;
//这里只验证我的结果是否正确
int isok=true;
for(int i=0;i<k;i++){
if(vi[i]!=vitp[i]){
isok=false;
break;
}
}
if(isok) cout<<"ok!"<<endl;
else cout<<"wrong!"<<endl;
return 0;
}
结果如下:
50000000 666
my BFPRT:
used time: 4.48627s
other BFPRT:
used time: 3.4281s
direct sorted and select top-k:
used time: 3.25237s
ok!