TOPK算法 - 线性遍历

作者: 半亩房顶 | 来源:发表于2019-03-04 16:20 被阅读7次

    应用场景

    在大量数据当中,寻找最大的几个值,完整排序可能会造成极大的不必要开销,所以TOPK算法很有掌握的必要

    思路

    今天主要是整理了小顶堆TOPK算法。常规快排作为对比项

    代码

    常规快排:

    这个快排写法相较于常见的交换位置的写法相比,更加舒服点

    <?php
    
        //为了测试运行内存调大一点
        ini_set('memory_limit', '2024M');
        ini_set("max_execution_time", "1200");
    
    
        //实现一个快速排序函数
        function quick_sort(array $array){
            $length = count($array);
            $left_array = array();
            $right_array = array();
            if($length <= 1){
                return $array;
            }
            $key = $array[0];
            for($i=1;$i<$length;$i++){
                if($array[$i] > $key){
                    $left_array[] = $array[$i];
                }else{
                    $right_array[] = $array[$i];
                }
            }
            $left_array = quick_sort($left_array);
            $right_array = quick_sort($right_array);
            return array_merge($left_array,array($key),$right_array);
        }
    
        //构造500w不重复数
        for($i=0;$i<5000000;$i++){
            $numArr[] = $i; 
        }
        //打乱它们
        shuffle($numArr);
    
        //现在我们从里面找到top10最大的数
        $start = microtime(true);
        var_dump(array_slice(quick_sort($numArr),0,10));
        $end = microtime(true);
        echo '耗时' . round($end - $start, 3) . '秒';
    

    结果如图:


    小顶堆:

    假如N为10,则先取出10个数,构建小顶堆,然后线性遍历一遍其余数据,大于堆顶的替换堆顶元素并重新调整一遍小顶堆

    <?php
    
        //为了测试运行内存调大一点
        ini_set('memory_limit', '2024M');
    
        //生成小顶堆函数
        function Heap(&$arr,$idx){
            $left  = ($idx << 1) + 1;
            $right = ($idx << 1) + 2;
    
            if (!$arr[$left]){
                return;
            }
    
            if($arr[$right] && $arr[$right] < $arr[$left]){
                $l = $right;
            }else{
                $l = $left;
            }
    
            if ($arr[$idx] > $arr[$l]){
                 $tmp = $arr[$idx]; 
                 $arr[$idx] = $arr[$l];
                 $arr[$l] = $tmp;
                 Heap($arr,$l);
            }
        }
    
        //这里为了保证跟上面一致,也构造500w不重复数
        /*
          当然这个数据集并不一定全放在内存,也可以在
          文件里面,因为我们并不是全部加载到内存去进
          行排序
        */
        for($i=0;$i<5000000;$i++){
            $numArr[] = $i;    
        }
        //打乱它们
        shuffle($numArr);
    
        $start = microtime(true);
        //先取出10个到数组
        $topArr = array_slice($numArr,0,10);
    
        //获取最后一个有子节点的索引位置
        //因为在构造小顶堆的时候是从最后一个有左或右节点的位置
        //开始从下往上不断的进行移动构造(具体可看上面的图去理解)
        $idx = floor(count($topArr) / 2) - 1;
    
        //生成小顶堆
        for($i=$idx;$i>=0;$i--){
            Heap($topArr,$i);
        }
    
        //这里可以看到,就是开始遍历剩下的所有元素
        for($i = count($topArr); $i < count($numArr); $i++){
            //每遍历一个则跟堆顶元素进行比较大小
            if ($numArr[$i] > $topArr[0]){
                //如果大于堆顶元素则替换
                $topArr[0] = $numArr[$i];
                /*
                  重新调用生成小顶堆函数进行维护,只不过这次是从堆顶
                  的索引位置开始自上往下进行维护,因为我们只是把堆顶
                  的元素给替换掉了而其余的还是按照根节点小于左右节点
                  的顺序摆放这也就是我们上面说的,只是相对调整下,并
                  不是全部调整一遍
                */
                Heap($topArr,0);
            }
        }
        var_dump($topArr);
        $end = microtime(true);
        echo '耗时' . round($end - $start, 3) . '秒';
    

    结果如图:


    总结

    其实最关键的思路在于线性遍历,先将小范围的数据排序,再线性遍历其它数据,这个排序算法的选择并不一定是堆排序。
    其实对比并不严谨,不过主要是为了说明常规排序和线性遍历思路的区别。

    相关文章

      网友评论

        本文标题:TOPK算法 - 线性遍历

        本文链接:https://www.haomeiwen.com/subject/smsiuqtx.html