摘要:基于堆排序算法和分治法求解千万级数据最值排序的Top K问题。

问题描述

  这是在网上找到的一道百度的面试题:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个检索串的长度为1-255字节。假设目前有一千万个记录,这些检索串的重复度比较高,虽然总数是1千万,但如果除去重复检索串后,总量不超过三百万个。一个检索串的重复度越高,说明查询它的用户越多,也就是越热门。请你统计最热门的10个检索串,要求使用的内存不能超过1G。

问题解析

  要统计最热门查询,首先就是要统计每个检索串出现的次数,然后根据统计结果,找出Top 10。所以我们可以基于这个思路分两步来设计该算法,下面分别给出这两步的算法。

第一步:查询次数统计

  从题目可以得知,虽然有一千万个Query,但是由于重复度比较高,因此事实上只有300万的Query,每个Query255Byte,因此我们可以考虑把它们都放进内存中去。而现在只是需要一个合适的数据结构,在这里,哈希表绝对是我们优先的选择,因为哈希表的查询速度非常的快,几乎是O(1)的时间复杂度。那么,我们的算法就有了:维护一个Key为Query字串,Value为该Query出现次数的哈希表,每次读取一个Query,如果该字串不在哈希表中,那么加入该字串,并且将Value值设为1;如果该字串在哈希表中,那么将该字串的计数加一即可。最终我们在O(N)的时间复杂度内完成了对该海量数据的处理。

  该方法只需要IO数据文件一次,因此该算法在工程上有更好的可操作性。另外,key也可以换成Query的哈希值(如MD5的值),以节约内存。

第二步:找出Top 10

算法一:排序

  我想对于排序算法大家都已经不陌生了,这里不再赘述,我们要注意的是排序算法的时间复杂度是NlgN,在本题目中,三百万条记录,用1G内存是可以存下的。

算法二:部分排序

  题目要求是求出Top 10,因此没有必要对所有的Query都进行排序,我们只需要维护一个10个大小的数组,初始化放入10Query,按照每个Query的统计次数由大到小排序,然后遍历这300万条记录,每读一条记录就和数组最后一个Query对比,如果小于这个Query,那么继续遍历,否则,将数组中最后一条数据淘汰,加入当前的Query。最后当所有的数据都遍历完毕之后,那么这个数组中的10个Query便是我们要找的Top10了。不难分析出,这样的算法的时间复杂度是N*K, 其中K是指top多少。

算法三:堆排序

  在算法二中,我们已经将时间复杂度由NlogN优化到NK,不得不说这是一个比较大的改进了,可是有没有更好的办法呢?分析一下,在算法二中,每次比较完成之后,需要的操作复杂度都是K,因为要把元素插入到一个线性表之中,而且采用的是顺序比较。这里我们注意一下,该数组是有序的,我们每次查找的时候可以采用二分的方法查找,这样操作的复杂度就降到了logK,可是,随之而来的问题就是数据移动,因为移动数据次数增多了。不过,这个算法还是比算法二有了改进。

  基于以上的分析,我们想想,有没有一种既能快速查找,又能快速移动元素的数据结构呢?回答是肯定的,那就是堆。借助堆结构,我们可以在log量级的时间内查找和调整/移动。因此到这里,我们的算法可以改进为这样,维护一个元素个数为k(该题目中是10小的小根堆,然后遍历300万的Query,分别和根元素进行对比。最小堆构建过程如下:

1、构造初始堆

  遍历待排序的N个数,把最先遍历到的k个数存放到最小堆中,并假设它们就是我们要找的最大的K个数,然后构造第一个小根堆。

2、首尾交换,断尾重构

  对断尾后剩余部分重新构造小根堆

3、迭代执行第二步

  将第二步进行重复,直到首尾重叠,排序完成

  注意:大根堆排序后是从小到大,小根堆排序后是从大到小。那么这样,这个算法发时间复杂度就降到了NlogK,和算法二相比,又有了比较大的改进。

算法四:分治法

   借助快速排序方法获取Top K。思路如下:

  (1)比如有10亿的数据,找出Top 1000,我们先将10亿的数据分成1000份,每份100万条数据。

  (2)在每一份中找出对应的Top 1000,整合到一个数组中,得到100万条数据,这样过滤掉了90%的数据。

  (3)使用快速排序对这100万条数据进行一轮排序,一轮排序之后指针的位置指向的数字假设为S,会将数组分为两部分,一部分大于S记作Si,一部分小于S记作Sj。

  (4)如果Si元素个数大于1000,我们对Si数组再进行一轮排序,再次将Si分成了Si和Sj。如果Si的元素小于1000,则我们需要在Sj中获取1000-count(Si)个元素的,也就是对Sj进行排序

  (5)如此递归下去即可获得Top K。

  时间复杂度与空间复杂度:(1)时间复杂度:一份获取前TopK的时间复杂度:O((N/n)logK)。则所有份数为:O(NlogK),但是分治法我们会使用多核多机的资源,比如我们有S个线程同时处理。则时间复杂度为:O((N/S)logK)。之后进行快排序,一次的时间复杂度为:O(N),假设排序了M次之后得到结果,则时间复杂度为:O(MN)。所以 ,总时间复杂度大约为O(MN+(N/S)logK) 。(2)空间复杂度:需要每一份一个数组,则空间复杂度为O(N)。

小结

  至此,我们的算法就完全结束了,经过步骤一和步骤二的最优结合,最终的时间复杂度是O(N) + O(N’)logK。如果各位有什么好的算法,欢迎跟帖讨论。

利用堆排序和分治法求解千万级数据排序的Top K问题—百度面试的更多相关文章

  1. 分治法求解最近对问题(c++)

    #include"stdafx.h" #include<iostream> #include<cmath> #define TRUE 1 #define F ...

  2. Java算法——分治法

         一.基本概念 在计算机科学中,分治法是一种很重要的算法.字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简 ...

  3. [C++] 分治法之棋盘覆盖、循环赛日程表

    一.分治的基本思想 将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之. 对于一个规模为 n 的问题,若问题可以容易地解决,则直接解决,否则将其分解为 k 个规模较小的子 ...

  4. 分治法及其python实现例子

    在前面的排序算法学习中,归并排序和快速排序就是用的分治法,分治法作为三大算法之一的,有非常多的应用例子. 分治法概念 将一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题-- ...

  5. 分治法 - Divide and Conquer

    在计算机科学中,分治法是一种很重要的算法.分治法即『分而治之』,把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……直到最后子问题可以简单的直接求解,原问题的解即子问题的 ...

  6. 分治法避免定义多个递归函数,应该使用ResultType

    总结:对二叉树应用分治法时,应避免定义多个递归函数,当出现需要递归求解多种的结果时,尽量使用ResultType来让一次递归返回多种结果. 题目:Binary Tree Maximum Path Su ...

  7. 分治法(一)(zt)

    这篇文章将讨论: 1) 分治策略的思想和理论 2) 几个分治策略的例子:合并排序,快速排序,折半查找,二叉遍历树及其相关特性. 说明:这几个例子在前面都写过了,这里又拿出来,从算法设计的策略的角度把它 ...

  8. C语言实现快速排序法(分治法)

    title: 快速排序法(quick sort) tags: 分治法(divide and conquer method) grammar_cjkRuby: true --- 算法原理 分治法的基本思 ...

  9. p1257 平面上最接近点对---(分治法)

    首先就是一维最接近点的情况... #include<iostream> #include<cstdio> #include<cstring> #include< ...

  10. MySQL千万级数据分区存储及查询优化

    作为传统的关系型数据库,MySQL因其体积小.速度快.总体拥有成本低受到中小企业的热捧,但是对于大数据量(百万级以上)的操作显得有些力不从心,这里我结合之前开发的一个web系统来介绍一下MySQL数据 ...

随机推荐

  1. 写了个 CasaOS/ZimaOS 内网穿透的远程访问插件(不是 frp 或者 nps),欢迎大家测试使用

    插件正在提交,应该过几天就会进入市场了. 插件访问效果大概如下: casaOS 远程界面 如果大家想先行测试可以手动下载 pr 的文件进行测试. 使用 插件会提供一个二维码,使用OpenIoThub ...

  2. css设置backgroud:url(),无效

    react项目中,使用styled-components编写样式,给元素添加背景图不生效. background直接设置十六进制颜色或者颜色的英文名称都是可行的,但是使用url无作用,那就是url问题 ...

  3. Proteus中数码管动态扫描显示不全(已解决)

    前言 我是直接把以前写的 51 数码管程序复制过来的,当时看的郭天祥的视频,先送段选,消隐后送位选,最后来个 1ms 的延时. 代码在 Proteus 中数码管静态是可以的,动态显示出了问题--显示不 ...

  4. goland无法识别包

    新建 Go 项目时,一定要通过 "File -> New -> Project..." 方式建立,千万不要通过 "File -> Open", ...

  5. C++文件读写常用操作

    C++文件读写常用操作 最近的毕设工作要使用C++保存读取一些矩阵,此篇博文记录比较好用的C++文件读写操作. 写 #include <iostream> #include <fst ...

  6. CoreOS 手动升级篇

    说到升级...通常肯定会以下2个步骤: 检查是否有新版本. 下载和安装新版本. 在 CoreOS 中也一样,我们先来看下在 CoreOS 中对应的命令: # 检查是否有新版本 update_engin ...

  7. D的SDK的设置

    有点烦,被困扰.看大虾的文章一并感谢: 进入D:\Users\Public\Documents\Embarcadero\Studio\22.0\CatalogRepository\AndroidSDK ...

  8. CH9121 FTP使用详解

    一.FTP简介: FTP是基于TCP应用层的网络文件传输协议,支持两种模式:Standard (PORT方式,主动方式),Passive (PASV,被动方式).采用明文通信不加密. 1.Port模式 ...

  9. .net WorkFlow 流程设计

    WikeFlow官网:www.wikesoft.com WikeFlow学习版演示地址:workflow.wikesoft.com WikeFlow学习版源代码下载:https://gitee.com ...

  10. React项目报错:Element type is invalid: expected a string可能的原因

    React项目报错:Element type is invalid: expected a string  起因:React使用Antd组件库,因为某些原因实在用不下去了,代码不变直接改成Tdesig ...