BFPRT实现

这里自己实现了一下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!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值