堆应用:优先级队列
优先级队列,首先是一个队列,但是并非遵守先进先出的原则,而是根据队列总元素的优先级高低出列。
优先级队列应用的场景非常多,后续总结的数据结构和算法都要依赖于它。比如:赫夫曼编码,图的最短路径,最小生成树等等。
举例一:合并有序小文件。
假如有 100 个文件,每个文件 100M,文件中存储的都是有序字符串,现在要将这些文件合并成一个有序的大文件。
第一种方法,从 100 个文件中各取一个,放在一个数组中,然后比较出最小的元素,放入大文件中。如果该元素是从 18 号文件中取出的,那么再次从该文件中取出一个元素,放入数组中进行对比,得到最小元素依旧放入大文件中。如此往复,最终完成合并。
第二种方法:从 100 个文件中各取一个元素,将这些元素构建小顶堆。堆顶元素就是最小的元素,然后取出堆顶元素放入大文件中。如果该元素是从 18 号文件中取出的,那么再次从该文件中取出一个元素,插入堆中。如此往复,最终实现合并。
第一种方法查找最小元素时间复杂度是 O(n),而第二种方法删除堆顶元素以及插入元素,时间复杂度都是 O(logn)。
举例一:高性能定时器。
假如一个定时器维护了很多定时任务,为了保证这些任务执行的准确性,我们可能要间隔很短的时间就检测一遍任务列表,看看当下是否有任务需要执行。这种方法,每次都要遍历一次任务列表,非常浪费性能。
我们根据定时任务距离下一次执行的时间长短,构建一个小顶堆,堆顶元素就是即将执行的任务。首先定时器获取对顶元素,判断下一次定时任务执行的具体时间,然后定时任务设定在任务执行前一秒再来获取任务进行执行。栈顶元素出队,然后下一个距离执行时间最短的元素就是堆顶元素,优先执行。这样,定时器就不用频繁地扫描任务列表,只需在需要的时候扫描任务列表就行。
堆应用:求出 Top k。
求出 Top k 分两种情况,一种是针对静态数据的统计,一种是针对动态数据。
针对静态数据,在一个有 n 个元素的数组中求 Top k。维护一个大小为 k 的小顶堆,从数组中取出一个元素与堆顶元素对比。若是大于堆顶元素,则删除堆顶元素然后插入堆中,若是小于堆顶元素,则丢弃。遍历堆中元素,时间复杂对为 O(n)。堆中插入元素时间复杂度为 O(logk),最坏情况下 n 个元素每个都插入一次,时间复杂度为 O(nlogk)。
针对动态数据,往集合中插入元素的时候,都需要与前 k 个元素对比,然后确定这个元素是否进入前 k 个元素的集合。这样的话查找最小元素的时间复杂度为 O(logk),插入 n 个数据,时间复杂度就是 O(nlogk)。如果我们始终维护一个小顶堆,那么每次往数组插入数据的时候,只需跟堆顶元素对比就可以了,不需要个所有元素对比,节省时间。
堆应用:求中位数。
对于一组静态数据,直接排序。如果数据个数是个奇数,则第 n/2 + 1 个元素是中位数。如果数据个数是个偶数,那中间位置 n/2,n/2 + 1 都是中间位置上的元素,我们取靠前的元素,第 n/2 个元素为中位数。
对于动态数据,我们首先维护两个堆。一个是大顶堆,一个小顶堆。对于数据个数是偶数的,大顶堆存储数据的前 n/2 个元素,小顶堆存储数据的后 n/2 个元素。对于数据个数是奇数的,大顶堆存储数据的前 n/2 + 1 个元素,小顶堆存储数据的后 n/2 个元素。那么大顶堆的堆顶元素就是中位数。
如果需要插入数据,该数据小于等于大顶堆的元素的时候,将其放入大顶堆。反之,放入小顶堆。在这个过程当中,我们要始终保证两个堆中的元素个数满足前面的约定:偶数个元素,大顶堆元素个数是 n/2,小顶堆元素个数也是 n/2;奇数个元素,大顶堆元素个数是 n/2 + 1,小顶堆元素个数也是 n/2。如果那个堆不满足上述的约定,就将堆顶元素移动到另外一个堆中就可以了。
综合题
针对 10亿个关键词搜索日志,如何快速找到 Top 10 的关键词。硬性条件:单机,内存 1GB。
因为关键词有重复,所以可以利用散列表,平衡二叉查找树等支持快速查找快速插入的数据结构来统计关键词出现的次数。
针对统计过次数的数据,可以利用堆求 Top k 的方法求出排名前十的关键词。
但是,如果最终不重复的关键词有 1亿个,平均长度为 50 字节,那么就需要 5 GB 的内存存储。而我们的内存只有 1GB,肯定不够用了。
可以这样做:创建十个空文件,利用哈希算法对 10亿个关键词求值,最后与 10 求模,确定该关键词需要放入的文件。
10亿数据分片后,可能每个文件只有 1亿个数据,去除重复可能只有 1000万个数据,平均长度为 50字节,占用内存只有 500M。
针对分片后的数据,利用散列表和堆,求出 Top 10 的关键词,然后将这 100 个关键词再次求 Top 10,就得出最终结果了。
总结
本文创作灵感来源于 极客时间 王争老师的《数据结构与算法之美》课程,通过课后反思以及借鉴各位学友的发言总结,现整理出自己的知识架构,以便日后温故知新,查漏补缺。
初入算法学习,必是步履蹒跚,一路磕磕绊绊跌跌撞撞。看不懂别慌,也别忙着总结,先读五遍文章先,无他,唯手熟尔~
与诸君共勉