PHP利用二叉堆实现TopK-算法的方法详解


Posted in PHP onApril 24, 2017

前言

在以往工作或者面试的时候常会碰到一个问题,如何实现海量TopN,就是在一个非常大的结果集里面快速找到最大的前10或前100个数,同时要保证内存和速度的效率,我们可能第一个想法就是利用排序,然后截取前10或前100,而排序对于量不是特别大的时候没有任何问题,但只要量特别大是根本不可能完成这个任务的,比如在一个数组或者文本文件里有几亿个数,这样是根本无法全部读入内存的,所以利用排序解决这个问题并不是最好的,所以我们这里就用php去实现一个小顶堆来解决这个问题.

二叉堆

二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树,二叉堆有两种,最大堆 和 最小堆,最大堆:父结点的键值总是大于或等于任何一个子节点的键值;最小堆:父结点的键值总是小于或等于任何一个子节点的键值

PHP利用二叉堆实现TopK-算法的方法详解
小顶堆-(图片来自网络)

二叉堆一般用数组来表示(看上图),例如,根节点在数组中的位置是0,第n个位置的子节点分别在2n+1和 2n+2,因此,第0个位置的子节点在1和2,1的子节点在3和4,以此类推,这种存储方式便於寻找父节点和子节点。

具体概念问题这里就不在多说了,如果对二叉堆有疑问的可以在好好了解下这个数据结构,下面我们就针对上述topN问题来用php代码实现并解决,为了看出区别这里先用排序的方式去实现下看下效果如何。

利用快速排序算法来实现 TopN

//为了测试运行内存调大一点
ini_set('memory_limit', '2024M');

//实现一个快速排序函数
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){
   $right_array[] = $array[$i];
  }else{
   $left_array[] = $array[$i];
  }
 }
 $left_array = quick_sort($left_array);
 $right_array = quick_sort($right_array);
 return array_merge($right_array,array($key),$left_array); 
}

//构造500w不重复数
for($i=0;$i<5000000;$i++){
 $numArr[] = $i; 
}
//打乱它们
shuffle($numArr);

//现在我们从里面找到top10最大的数
var_dump(time());
print_r(array_slice(quick_sort($all),0,10));
var_dump(time());

PHP利用二叉堆实现TopK-算法的方法详解

运行之后结果

可以看到上面打印出了top10的结果,并输出了下运行时间,大概99s左右,但这只是500w个数且全部能装入内存的情况,如果我们有一个文件里面有5kw或5亿个数,肯定就会有些问题了.

利用二叉堆算法来实现 TopN

实现流程是:

     1、先读取10个或100个数到数组里面,这就是我们的topN数.

     2、调用生成小顶堆函数,把这个数组生成一个小顶堆结构,这个时候堆顶一定是最小的.

     3、从文件或者数组依次遍历剩余的所有数.

     4、每遍历出来一个则跟堆顶的元素进行大小比较,如果小于堆顶元素则抛弃,如果大于堆顶元素则替换之.

     5、跟堆顶元素替换完毕之后,在调用生成小顶堆函数继续生成小顶堆,因为需要再找出来一个最小的.

     6、重复以上4~5步骤,这样当全部遍历完毕之后,我们这个小顶堆里面的就是最大的topN,因为我们的小顶堆永远都是排除最小的留下最大的,而且这个调整小顶堆速度也很快,只是相对调整下,只要保证根节点小于左右节点就可以.

     7、算法复杂度的话按top10最坏的情况下,就是每遍历一个数,如果跟堆顶进行替换,需要调整10次的情况,也要比排序速度快,而且也不是把所有的内容全部读入内存,可以理解成就是一次线性遍历.

//生成小顶堆函数
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);

//先取出10个到数组
$topArr = array_slice($numArr,0,10);

//获取最后一个有子节点的索引位置
//因为在构造小顶堆的时候是从最后一个有左或右节点的位置
//开始从下往上不断的进行移动构造(具体可看上面的图去理解)
$idx = floor(count($topArr) / 2) - 1;

//生成小顶堆
for($i=$idx;$i>=0;$i--){
 Heap($topArr,$i);
}

var_dump(time());
//这里可以看到,就是开始遍历剩下的所有元素
for($i = count($topArr); $i < count($numArr); $i++){
 //每遍历一个则跟堆顶元素进行比较大小
 if ($numArr[$i] > $topArr[0]){
  //如果大于堆顶元素则替换
  $topArr[0] = $numArr[$i];
  /*
   重新调用生成小顶堆函数进行维护,只不过这次是从堆顶
   的索引位置开始自上往下进行维护,因为我们只是把堆顶
   的元素给替换掉了而其余的还是按照根节点小于左右节点
   的顺序摆放这也就是我们上面说的,只是相对调整下,并
   不是全部调整一遍
  */
  Heap($topArr,0);
 }
}
var_dump(time());

PHP利用二叉堆实现TopK-算法的方法详解

运行之后结果

可以看到最终的结果也是top10,只不过时间只用了1s左右,而且无论是内存还是时间效率都满足我们的要求,而且跟排序比最好的一点就是不用把所有的数据集都读如到内存里面来,因为我们不需要排序,而上面是为了演示,所以直接在内存构造了500w元素,然而我们可以把这个全部转移到文件里面去,然后一行一行读取进行比较,因为我们这个数据结构的核心点就是线性遍历跟内存里面很小的小顶堆结构进行比较,最终得到TopN.

总结

最后想说的就是 算法+数据结构 真的非常重要,一个好的算法可以使我们的效率大大提高。好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

PHP 相关文章推荐
使用无限生命期Session的方法
Oct 09 PHP
PHP中MD5函数使用实例代码
Jun 07 PHP
php设计模式 Bridge (桥接模式)
Jun 26 PHP
对象失去焦点时自己动提交数据的实现代码
Nov 06 PHP
使用php批量删除数据库下所有前缀为prefix_的表
Jun 09 PHP
PHP文件上传判断file是否己选择上传文件的方法
Nov 10 PHP
thinkPHP实现递归循环栏目并按照树形结构无限极输出的方法
May 19 PHP
Laravel 的数据库迁移的方法
Jul 31 PHP
Phpstorm+Xdebug断点调试PHP的方法
May 14 PHP
PHP SESSION机制的理解与实例
Mar 22 PHP
php使用json-schema模块实现json校验示例
Sep 28 PHP
如何用PHP实现多线程编程
May 26 PHP
关于PHP定时发送服务的解决办法
Apr 23 #PHP
php读取和保存base64编码的图片内容
Apr 22 #PHP
PHP7多线程搭建教程
Apr 21 #PHP
mac系统下安装多个php并自由切换的方法详解
Apr 21 #PHP
php获取excel文件数据
Apr 21 #PHP
PHP实现限制IP访问的方法
Apr 20 #PHP
PHP简单读取xml文件的方法示例
Apr 20 #PHP
You might like
php的access操作类
2008/04/09 PHP
浅析php中三个等号(===)和两个等号(==)的区别
2013/08/06 PHP
php中unserialize返回false的解决方法
2014/09/22 PHP
php短址转换实现方法
2015/02/25 PHP
Swoole-1.7.22 版本已发布,修复PHP7相关问题
2015/12/31 PHP
功能强大的PHP发邮件类
2016/08/29 PHP
Laravel-添加后台模板AdminLte的实现方法
2019/10/08 PHP
基于jQuery的固定表格头部的代码(IE6,7,8测试通过)
2010/05/18 Javascript
javascript与CSS复习(二)
2010/06/29 Javascript
Jquery实现自定义tooltip示例代码
2014/02/12 Javascript
jquery自定义滚动条插件示例分享
2014/02/21 Javascript
使用jQuery.wechat构建微信WEB应用
2014/10/09 Javascript
JavaScript通过setTimeout实时显示当前时间的方法
2015/04/16 Javascript
Windows系统下Node.js的简单入门教程
2015/06/23 Javascript
windows下安装nodejs及框架express
2015/08/07 NodeJs
javascript引用类型之时间Date和数组Array
2015/08/27 Javascript
JavaScript获取当前运行脚本文件所在目录的方法
2016/02/03 Javascript
详解JavaScript中|单竖杠运算符的使用方法
2016/05/23 Javascript
基于JS实现密码框(password)中显示文字提示功能代码
2016/05/27 Javascript
JavaScript创建对象的七种方式全面总结
2017/08/21 Javascript
JavaScript图片处理与合成总结
2018/03/04 Javascript
el-form 多层级表单的实现示例
2020/09/10 Javascript
利用vue3+ts实现管理后台(增删改查)
2020/10/30 Javascript
[01:53]DOTA2超级联赛专访Zhou 五年职业青春成长
2013/05/29 DOTA
[49:43]VG vs FNATIC 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
python的正则表达式re模块的常用方法
2013/03/09 Python
Python2.7读取PDF文件的方法示例
2017/07/13 Python
Python如何使用bokeh包和geojson数据绘制地图
2020/03/21 Python
Python 操作 PostgreSQL 数据库示例【连接、增删改查等】
2020/04/21 Python
手把手教你如何用Pycharm2020.1.1配置远程连接的详细步骤
2020/08/07 Python
美味咖啡的顶级烘焙师:Cafe Britt
2018/03/15 全球购物
优秀英语专业毕业生求职信
2013/11/23 职场文书
导师推荐信范文
2014/05/09 职场文书
2015年部门工作总结范文
2015/03/31 职场文书
关于社会实践的心得体会(2016最新版)
2016/01/25 职场文书
小学三年级作文之写景
2019/11/05 职场文书