优先级队列,顾名思义,它首先应该是一个队列。队列最大的特性就是先进先出,而在优先级队列中,数据的出队顺序则是按照优先级来,优先级高的先出队。算法
实现优先级队列的方法有不少,可是用堆来实现是最直接、最高效的。堆和优先级队列很是类似,一个堆就能够看做一个优先级队列。从优先级队列中取出优先级最高的元素,就至关于取出堆顶元素。数组
假设咱们有 100 个小文件,每一个文件的大小是 100 MB,每一个文件中存储的都是有序的字符串,如今咱们要将这些小文件合并成一个有序的大文件,就要用到优先级队列。数据结构
总体思路有点像归并排序的合并操做。咱们从这 100 个文件中各自取出第一个字符串放入到数组中,而后比较大小,将最小的字符串放入合并后的文件,并从数组中删除。性能
假如这个最小的字符串来自文件 1.txt,那么咱们就再从这个文件中取出下一个字符串放入数组,从新进行比较,找出最小的字符串加入到大文件中,而后从数组中删除。以此类推,直到咱们遍历完全部小文件为止。spa
这里,咱们每次从数组中找出最小的字符串都要进行一遍遍历。显然,这不是很高效。排序
其实,咱们就能够把数组改为优先级队列,或者说是堆。咱们把从小文件中取出来的字符串放入到小顶堆,那么,堆顶的元素就是最小的也就是优先级最高的元素。每次,咱们都将堆顶元素放入到大文件中并将其从堆顶删除,而后再取出下一个字符串放入堆中,直到全部小文件都遍历完毕便可。接口
删除堆顶元素和往堆中插入数据的时间复杂度都为 $O(logn)$,$n$ 表明堆中的数据个数,这里就是 100,比数组快多了。队列
假设咱们有一个定时器,定时器中维护了不少定时任务,每一个任务都设定了一个 要触发执行的时间点。定时器每隔一个很小的时间单位(好比 1 秒)就会扫描一遍任务,看是否有任务须要执行。rem
可是,这样作就很是低效。首先,若是距离任务执行时间点还太远,那么许多的扫描都是徒劳的。其次,每次咱们都须要扫描整个任务列表,若是任务列表很大的话,就会比较耗时。字符串
针对此,咱们就能够按照任务执行时间的前后顺序来创建一个优先级队列,优先级最高的任务就是小顶堆的堆顶元素。
咱们拿堆顶元素的执行时间点,与当前时间点相减,获得一个时间间隔 T。也就是说,从当前时间点再等待 T 时间,才有第一个任务须要执行。在这期间,咱们就无需再查询了。等到 T 时间后,咱们取出堆顶任务执行,而后再从新计算差值,继续等待。
求 Top K 的问题能够分为两类。一类是静态数据,数据集合事先知道,不会再变。另外一类是动态数据,数据集合事先并不知道,有数据动态地加入到数据集合中。
针对静态数据,咱们能够维护一个大小为 K 的小顶堆。顺序遍历数组,若是数据小于堆顶元素,则不做处理继续遍历;若是数据大于堆顶元素,则删除堆顶元素并将当前数据插入到堆中。遍历完数组后,堆中数据即为前 K 大元素。
遍历数据须要 $O(n)$ 的时间复杂度,而每一次堆操做须要 $O(logk)$ 的时间复杂度,因此最坏状况下,$n$ 个元素都入堆,时间复杂度为 $O(nlogk)$。
针对动态数据,咱们能够一直维护一个大小为 K 的小顶堆,每当有新数据加入到集合中时,咱们就拿它和堆顶元素进行比较,而后按照和上面静态数据同样的策略更新堆。这样,不管任什么时候候须要查询前 K 大数的时候,咱们均可以直接返回队中的元素便可。
所谓中位数,就是处于中间位置的数字。若是数据的个数为奇数,那么第 $\frac{n}{2}+1$ 个数据就是中位数;若是数据的个数为偶数,那么于中间位置的数字有两个,咱们能够取第 $\frac{n}{2}+1$ 或第 $\frac{n}{2}$ 个数据做为中位数。
对于静态数据,咱们能够先对数据进行排序,而后取出中间位置的数据便可。虽然排序的代价比较大,可是边际成本会很小,咱们只须要排序一次。
可是,针对动态数据,若是每次查询中位数的时候都对数据进行排序,那效率就不高了。借助堆这种数据结构,咱们不用排序,就能够很是高效地找到中位数。
咱们须要维护两个堆,一个大顶堆,一个小顶堆。大顶堆中存储前半部分数据,小顶堆中存储后半部分数据,并且小顶堆中的数据都大于大顶堆中的数据。这时候,大顶堆的堆顶元素也就是咱们要找的中位数。
若是是偶数状况,那么大顶堆就有 $\frac{n}{2}$ 个数据,小顶堆就有 $\frac{n}{2}$ 个数据;若是是奇数状况,那么大顶堆就有 $\frac{n}{2}+1$ 个数据,小顶堆就有 $\frac{n}{2}$ 个数据。
若是新加入的数据小于等于大顶堆的堆顶元素,咱们就将这个数据插入到大顶堆;不然,咱们就将这个数据插入到小顶堆。
这时候,就有可能会出现两个堆中的数据个数不符合前面的约定。咱们能够从一个堆中不停地将堆顶元素移动到另外一个堆中,来让两个堆中的数据个数从新知足上面的约定。
除此以外,咱们还能够利用一样的原理来快速求出其余百分位的数据。假如咱们要找出接口的 99% 响应时间?
所谓 99% 的响应时间,就是对响应时间排完序后处于 99%n 位置的数据。咱们依然创建一个大顶堆和一个小顶堆,其中大顶堆中保存 99%n 的数据,而小顶堆中保存 1%*n 的数据,而后依然按照上面处理中位数的操做对两个堆进行维护。
获取更多精彩,请关注「seniusen」!