BFPRT线性查找算法

Wesley13
• 阅读 735

介绍:

BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分 析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂 度,五位算法作者做了精妙的处理。

时间复杂度

O(N)

算法步骤:

1. 将n个元素每5个一组,分成n/5(上界)组。

**

2. 取出每一组的中位数,任意排序方法,比如插入排序。

3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。

4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。

5. 若i==k,返回x;若i<k,在小于x的元素中递归查找第i小的元素;若i>k,在大于x的元素中递归查找第i-k小的元素。

终止条件:n=1时,返回的即是i小元素。

**

测试结果:

数组里有10000个随机数, 执行500次后花费的毫秒数
最大毫秒数为208
最小毫秒数为86
平均毫秒数为102
查找失败次数0

疑问:

这里的代码示例耗时过长,高达100ms,比排序获取第k小的数字还慢,不知道哪里出了问题,请大家指教。

代码示例:

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>BFPRT线性查找算法</title>
</head>
<body>
<script>
var count = 10000,
    num = testCount = 500,
    i,
    exeCount,
    exeTimeArr = [],
    k,
    failArr = [],

    sortFn = function(a, b){
        return a - b;
    },

    bfprtSearch = function(a, k){

        var arrs = [],
            middle,
            middleArr = [],
            middleIndex,
            sortedArr = [],
            last,
            i, j, h, len;

        if(a.length <= 5){

            if(a.length == 1) return a[0];

            a.sort(sortFn);
            return a[k-1];
        }
        
        // 以5个数字为一组,排序并记录中位数
        while(a.length >= 5){
            sortedArr = a.splice(0, 5).sort(sortFn);
            middleArr.push( sortedArr[2] );
            arrs.push(sortedArr);
        }
        last = a;
    
        a = [];
        groupNum = arrs.length;

        // 得到排序后的数组
        while(arrs.length){
            a = a.concat( arrs.shift() );
        }
        a = a.concat(last);
        //console.log('得到排序后的数组', a);

        // 中位数前置
        for(i=0,len=middleArr.length;i<len;i++){
            middleIndex = i * 5 + 2;
            a[i] = [ a[middleIndex], a[middleIndex] = a[i] ] [0];
        }
        //console.log('中位数前置', a);

        // 获取中位数数组中的中位数
        middleArr.sort(sortFn);
        middleIndex = Math.floor( (len - 1) / 2);
        middle = middleArr[middleIndex];
        //console.log('获取中位数数组中的中位数', middle);

        // 根据上一步得到的中位数对数据进行交换
        for(i=0,j=a.length-1;j>i;j--){

            if(a[j] > middle){
                continue;
            }else{
                while(a[i] < middle){ // 小于对比中位数则略过
                    i++;

                    if(i > j) break;
                }
            }

            if(i > j){
                break;
            }else{
                a[j] = [ a[i], a[i] = a[j] ] [0]; // 将小于对比中位数的数前置
            }
        }
        //console.log('根据上一步得到的中位数对数据进行交换', a, j, k);

        h = j + 1;
        if(h+1 == k){ // 前面有k-1个数,刚好获取到
            return middle;

        }else if(h+1 > k){ // 第k小的数在前段数组里
            return bfprtSearch( a.slice(0, h), k );

        }else{ // 第N小的数在后段数组里,重置第k小的k值
            return bfprtSearch( a.slice(h), k - h);
        }

        return a.slice(0, j);
 
    };

var body = document.getElementsByTagName('body')[0];
var log = function(s){

    var p = document.createElement('p');
    var ps = document.getElementsByTagName('p');

    p.innerHTML = s;

    ps.length ?
        body.insertBefore(p, ps[0]) :
        body.appendChild(p);

    //document.write(s + '<br />'); 破坏文档结构
    //firefox下使用console.log并开启firebug会影响执行
}

function exeTest( callback ){

    function test(){
        var cost,
            startTime,
            result,
            arr = [], 
            sortArr,
            tmp;

        testCount--;

        // 构建随机数组
        for(i=0;i<count;i++){
            arr.push( Math.round(Math.random() * count) ); // 0 - count
        }

        k = Math.floor(Math.random() * count) + 1;

        tmp = arr.slice(0);
        sortArr = arr.slice(0).sort(sortFn);

        //console.log(arr);
        //console.log(sortArr);
        
        startTime = + new Date();
        result = bfprtSearch(arr, k);

        //console.log(result == sortArr[k-1], result);

        if(result != sortArr[k-1]){
            tmp.unshift('K为'+ k + '!!!');
            failArr.push(tmp);
        }

        exeTimeArr.push(cost = new Date() - startTime); //花费的毫秒数

        log('本次测试查找'+ (result == sortArr[k-1] ? '成功' : '失败') +' 第'+ k +'小的数,花费 '+ cost +' 毫秒, 还剩 '+ testCount +' 次测试');

        if(testCount == 0 && typeof callback == 'function'){

            callback();

        }else{

            // 预防浏览器挂起
            setTimeout(test, 10);
        }
    }

    test();
}

function callback(){
    var sum = 0;
    var result = '数组里有'+ count +'个随机数, 执行'+ num +'次后花费的毫秒数' + '<br />' +
                      '最大毫秒数为' + Math.max.apply(null, exeTimeArr) + '<br />' +
                      '最小毫秒数为' + Math.min.apply(null, exeTimeArr) + '<br />';

    exeTimeArr.forEach(function(value) {
        sum += value;
    })

    result += '平均毫秒数为' + Math.round( sum / num ) + '<br />' ;
    result += '查找失败次数' + failArr.length ;

    if(failArr.length){

        result += '<br />失败的数组为:<br />';

        failArr.forEach(function(data){
            result += data.join(',') + '<br />';
        })

    }

    log(result);
}

// 开始测试
exeTest(callback);

</script>
</body>
</html>
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
林末 林末
3年前
折半查找-Python版(二分查找)
介绍二分查找也称折半查找(BinarySearch),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。前提必须待查找的序列有序时间复杂度O(log2n)原理1)确定该期间的中间位置K2)将查找的值t与arrayk比较,若相等,查找成功返回此位置;否则确定新的查找区域,继续二分
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Master公式计算递归时间复杂度
我们在算递归算法的时间复杂度时,Master定理为我们提供了很强大的便利!Master公式在我们的面试编程算法中除了BFPRT算法的复杂度计算不了之外,其他都可以准确计算!这里用求数组最大值的递归函数来举例:publicstaticintgetMax(intarr,intL,intR){if
Wesley13 Wesley13
3年前
Java面试总结(排序算法)
1.冒泡排序算法描述:两两比较,大的放后面2.选择排序算法描述:在m元数组中找到最小值的位置,然后将最小值的位置和第n(n0,1,2,....m1)位的值对调,排序k次则m元数组中前k(k<m)位的值已经排序好,m元数组中前k位的值不需要再进行排序,此时需要排序的元素只有mk个3.插入排序算
Wesley13 Wesley13
3年前
C++ 顺序表 代码实现
线性表存储在计算机中可以采用多种方式,以下是按照顺序存储方式实现:优点:查找很方便缺点:插入元素、删除元素比较麻烦,时间复杂度O(n)1ifndefSeqList_h2defineSeqList_h3include<iostream4usingnamespacestd;
菜园前端 菜园前端
1年前
什么是时间复杂度?
原文链接:什么是时间复杂度?定性描述该算法的运行时间,一个函数、用大O表示,例如O(1)、O(n)、O(logN)...常见的时间复杂度量级常数阶O(1)对数阶O(logN)线性阶O(n)线性对数阶O(nlogN)平方阶O(n²)立方阶O(n)K次方阶O(
分布式系统的主键生成方案对比 | 京东云技术团队
UUID​UUID(通用唯一识别码)是由32个十六进制数组成的无序字符串,通过一定的算法计算出来。为了保证其唯一性,UUID规范定义了包括网卡MAC地址、时间戳、名字空间(Namespace)、随机或伪随机数、时序等元素,以及从这些元素生成UUID的算法。
时间复杂度为 O (n^2) 的排序算法 | 京东物流技术团队
对于小规模数据,我们可以选用时间复杂度为O(n2)的排序算法。因为时间复杂度并不代表实际代码的执行时间,它省去了低阶、系数和常数,仅代表的增长趋势,所以在小规模数据情况下,O(n2)的排序算法可能会比O(nlogn)的排序算法执行效率高。不过随着数据规模增
京东云开发者 京东云开发者
2个月前
时间复杂度为 O(n^2) 的排序算法
作者:京东保险王奕龙对于小规模数据,我们可以选用时间复杂度为O(n2)的排序算法。因为时间复杂度并不代表实际代码的执行时间,它省去了低阶、系数和常数,仅代表的增长趋势,所以在小规模数据情况下,O(n2)的排序算法可能会比O(nlogn)的排序算法执行效率高