河南网站建设哪里好,中山专业找人公司,宁波网站建设服务公司电话,网站要怎么做的吗为什么评价算法性能是根据时间和空间复杂度#xff0c;而不是别的参数#xff1f;是因为计算机结构是冯诺依曼体系#xff0c;除了输入输出设备和控制器#xff0c;就剩下运算器和存储器了
问题引入
搜索引擎的热门搜索排行榜功能是如何实现的#xff1f;搜索引擎每天会…为什么评价算法性能是根据时间和空间复杂度而不是别的参数是因为计算机结构是冯诺依曼体系除了输入输出设备和控制器就剩下运算器和存储器了
问题引入
搜索引擎的热门搜索排行榜功能是如何实现的搜索引擎每天会接收大量的用户搜索请求把这些用户输入的搜索关键词记录下来然后再离线地统计分析得到最热门的 Top 10 搜索关键词。假设现在我们有一个包含 10 亿个搜索关键词的日志文件如何能快速获取到热门榜 Top 10 的搜索关键词呢
堆这种数据结构几个非常重要的应用优先级队列、求 Top K 和求中位数。
堆的应用一优先级队列
堆优先级队列 堆的核心功能就是取出堆顶数据大顶堆--最大值小顶堆--最小值。 使用方法 1.插入数据与堆顶比较决定插入与否比如小顶堆堆顶拿到最小值若新插入数据比它大就删掉堆顶最小值插入新数据从而堆中保留了TopK 2.取数据取最大值/最小值(如优先队列的优先级数值就是大顶堆堆顶即最大值入队后自下而上堆化得到最新的堆顶出队时直接出堆顶即可。 1、优先级队列中数据的出队顺序不是先进先出而是按照优先级来优先级最高的最先出队 2、用堆来实现是最直接、最高效的 3、往优先级队列中插入一个元素就相当于往堆中插入一个元素从优先级队列中取出优先级最高的元素就相当于取出堆顶元素。
合并有序小文件
假设我们有 100 个小文件每个文件的大小是 100MB每个文件中存储的都是有序的字符串。我们希望将这些 100 个小文件合并成一个有序的大文件。这里就会用到优先级队列。
方案一 1、使用一个数组从这100个文件中各自取出第一个字符串放入数组比较大小然后将最小的放入合并后的文件中并从数组中删除。 2、假设最小的字符串来自A文件那么再从A文件中取出第二个字符串放入比较数组中重新比较大小 3、重复执行步骤1直到所有文件中数据都放入合并后的大文件 缺点每次从数组中取最小字符串都需要循环遍历整个数组 方案二 使用优先级队列堆 1、从100个文件中取出第一个元素放到小顶堆中堆顶元素也即优先级队列队首就是最小字符串。将该字符串放入到合并后最终大文件中并从堆顶部删除 2、再从小文件中取出下一个元素放入堆顶循环该过程 3、删除堆顶元素和堆中插入元素的复杂度都是O(logn) 高性能的定时器
假设有一个定时器定时器中维护了很多定时任务每个任务都设定了一个要触发执行的时间点。定时器每过一个很小的单位时间比如 1 秒就扫描一遍任务看是否有任务到达设定的执行时间。如果到达了就拿出来执行 这种方式的缺点每过 1 秒就扫描一遍任务列表的做法比较低效第一任务的约定执行时间离当前时间可能还有很久这样前面很多次扫描其实都是徒劳的第二每次都要扫描整个任务列表如果任务列表很大的话势必会比较耗时。
优化方案
按照任务设定的执行时间将这些任务存储在优先级队列中队列首部也就是小顶堆的堆顶存储的是最先执行的任务。拿队首任务的执行时间点与当前时间点相减得到一个时间间隔 T定时器就可以设定在 T 秒之后再来执行任务。从当前时间点到T-1秒这段时间里定时器都不需要做任何事情 T 秒时间过去之后定时器取优先级队列中队首的任务执行。然后再计算新的队首任务的执行时间点与当前时间点的差值把这个值作为定时器执行下一个任务需要等待的时间
利用堆求Top K
一类静态数据集合
维护一个大小为 K 的小顶堆顺序遍历数组从数组中取出数据与堆顶元素比较如果比堆顶元素大把堆顶元素删除并且将这个元素插入到堆中如果比堆顶元素小则不做处理继续遍历数组最后堆中的数据就是前K大数据
遍历数组需要 O(n) 的时间复杂度一次堆化操作需要 O(logK) 的时间复杂度所以最坏情况下n 个元素都入堆一次时间复杂度就是 O(nlogK)
二类动态数据集合
一个数据集合中有两个操作一个是添加数据另一个询问当前的前 K 大数据。如果每次询问前 K 大数据我们都基于当前的数据重新计算的话那时间复杂度就是 O(nlogK)n 表示当前的数据的大小
方案
实际上可以一直维护一个 K 大小的小顶堆当有数据被添加到集合中时与堆顶的元素对比。如果比堆顶元素大把堆顶元素删除并且将这个元素插入到堆中如果比堆顶元素小则不做处理。无论任何时候需要查询当前的前 K 大数据都可以立刻返回给他
利用堆求中位数
如何求动态数据集合中的中位数中位数就是处在中间位置的那个数。如果数据的个数是奇数把数据从小到大排列那第 2n1 个数据就是中位数注意假设数据是从 0 开始编号的如果数据的个数是偶数的话那处于中间位置的数据有两个第 2n 个和第 2n1 个数据这个时候可以随意取一个作为中位数比如取两个数中靠前的那个就是第 2n 个数据 静态数据
对于静态数据中位数是固定的。可以先排序第 2n 个数据就是中位数。每次询问中位数的时候直接返回这个固定的值。所以尽管排序的代价比较大但是边际成本会很小。
动态数据
动态数据集合中位数在不停地变动。
需要维护两个堆一个大顶堆一个小顶堆。大顶堆中存储前半部分数据小顶堆中存储后半部分数据且小顶堆中的数据都大于大顶堆中的数据。如果有 n 个数据n 是偶数我们从小到大排序那前 2n 个数据存储在大顶堆中后 2n 个数据存储在小顶堆中。这样大顶堆中的堆顶元素就是我们要找的中位数。如果 n 是奇数情况是类似的大顶堆就存储 2n1 个数据小顶堆中就存储 2n 个数据 当添加一个数据时候如何调整两个堆使得大顶堆中的堆顶数据继续是中位数呢
如果新加入的数据小于等于大顶堆的堆顶元素将这个新数据插入到大顶堆否则将这个新数据插入到小顶堆可能情况如果 n 是偶数两个堆中的数据个数都是 2n如果 n 是奇数大顶堆有 2n1 个数据小顶堆有 2n 个数据这时候需要从一个堆中不停地将堆顶元素移动到另一个堆通过这样的调整来让两个堆中的数据满足上面的约定最终求中位数只需要返回大顶堆的堆顶元素就可以了所以时间复杂度就是 O(1)。如何快速求接口的 99% 响应时间
99% 响应时间。如果有 100 个接口访问请求每个接口请求的响应时间都不同比如 55 毫秒、100 毫秒、23 毫秒等我们把这 100 个接口的响应时间按照从小到大排列排在第 99 的那个数据就是 99% 响应时间也叫 99 百分位响应时间。
利用两个堆实现。一个大顶堆一个小顶堆。假设当前总数据的个数是 n大顶堆中保存 n*99% 个数据小顶堆中保存 n*1% 个数据。大顶堆堆顶的数据就是我们要找的 99% 响应时间。方法类似前面求中位数
解答开头
一个包含 10 亿个搜索关键词的日志文件如何快速获取到 Top 10 最热门的搜索关键词呢
多台机器可以采用map-reduce的解决方法
单机环境
方案一 1、首先统计每个关键词出现频率 2、采用散列表顺序扫描10亿个关键词存在次数1不存在记录次数为1遍历完成后散列表中就存储了不重复关键词以及出现次数 3、利用堆求Top K的方法建立一个小顶堆大小为10遍历散列表然后从散列表汇中依次取出每个搜索关键词以及对应的出现次数与堆顶中的搜索关键词进行对比。大就删除堆顶关键词替换为新的关键词小于则不作处理 缺点如果每个搜索关键词的平均长度是 50 个字节那存储 1 亿个关键词起码需要 5GB 的内存空间散列表因为要避免频繁冲突不会选择太大的装载因子所以消耗的内存空间就更多了。如果机器只有1G内存空间那么无法一次性将所有关键词加入内存 方案二 1、创建 10 个空文件 000102……09 2、遍历这 10 亿个关键词通过哈希算法对其求哈希值然后哈希值同 10 取模得到的就是搜索关键词应该被分到的文件编号 3、10 亿个关键词分片之后每个文件都只有 1 亿的关键词去除掉重复的可能就只有 1000 万个每个关键词平均 50 个字节所以总的大小就是 500MB。1GB 的内存完全可以放得下 4、每个包含 1 亿条搜索关键词的文件利用散列表和堆分别求出 Top 10然后把这个 10 个 Top 10 放在一块然后取这 100 个关键词中出现次数最多的 10 个关键词这就是这 10 亿数据中的 Top 10 最频繁的搜索关键词
总结
优先级队列是一种特殊的队列优先级高的数据先出队而不再像普通的队列那样先进先出。堆就可以看作优先级队列只是称谓不一样罢了。
求 Top K 问题又可以分为针对静态数据和针对动态数据只需要利用一个堆就可以做到非常高效率地查询 Top K 的数据。
求中位数实际上还有很多变形比如求 99 百分位数据、90 百分位数据等处理的思路都是一样的即利用两个堆一个大顶堆一个小顶堆随着数据的动态添加动态调整两个堆中的数据最后大顶堆的堆顶元素就是要求的数据。
思考
有一个访问量非常大的新闻网站我们希望将点击量排名 Top 10 的新闻摘要滚动显示在网站首页 banner 上并且每隔 1 小时更新一次。如果你是负责开发这个功能的工程师你会如何来实现呢
方案一1、实时建立散列表key是新闻的摘要value是点击量 2、建立一个10的小顶堆每隔一个小时扫描一次散列表根据点击量大小放入到小顶堆中扫描完散列表后即出现Top10 的新闻点击量。方案二1对每篇新闻摘要计算一个hashcode并建立摘要与hashcode的关联关系使用map存储以hashCode为key新闻摘要为值 2按每小时一个文件的方式记录下被点击的摘要的hashCode 3当一个小时结果后上一个小时的文件被关闭开始计算上一个小时的点击top10 4将hashcode分片到多个文件中通过对hashCode取模运算即可将相同的hashCode分片到相同的文件中 5针对每个文件取top10的hashCode使用MaphashCode,int的方式统计出所有的摘要点击次数然后再使用小顶堆大小为10计算top10, 6再针对所有分片计算一个总的top10,最后合并的逻辑也是使用小顶堆计算top10 7如果仅展示前一个小时的top10,计算结束 8如果需要展示全天需要与上一次的计算按hashCode进行合并然后在这合并的数据中取top10 9在展示时将计算得到的top10的hashcode转化为新闻摘要显示即可方案三 1维护两个散列表一个是一小时新增的点击量的散列表以新闻id为键点击次数为值。一个是全部点击量的散列表。每隔一小时把新增的散列表的数据同步到全部点击量的散列表。然后把这小时内有变化的全部点击量的散列表的数据即此小时有新增点击量的新闻数据和我们维护的10个元素小顶堆堆顶进行比较比堆顶的点击量大的则使用该元素替换堆顶再进行堆化。比堆顶点击量小的则不做处理。然后比较完根据堆顶的10个元素的id从数据库读取相应的新闻摘要显示在banner上。除此之外还要把变化后的全部点击量散列表同步到数据库。因为保存的是新闻id所以散列表长度不会很大所占用的内存也不会很大。而每个小时新增的访问量的新闻id数也不会很多毕竟很多人只会阅读热门消息。所以新增的点击量的新闻数据假设为k,则每小时同步小顶堆的时间负责度为o(klg 10); 2018-12-02