堆在实际开发中的使用有哪些?
优先级队列
队列的最大特性是先进先出,在优先级队列中,数据的出队顺序是按照优先级来的,优先级最高的最先出队。
优先级队列和堆很像,往优先级队列中插入一个元素,就相当于在堆中插入一个元素,从优先级队列中取出优先级最高的元素,就和取出堆顶元素一样。
合并有序小文件
假设有 100 个小文件,每个文件的大小是 100MB,每个文件中存储的都是有序的字符串。现在将这些100个小文件合并成一个有序的大文件。
第一种方法,我们可以从这一百个文件中,各取出第一个字符串,放入数组中,然后比较大小,把最小的那个字符放入合并后的大文件中,然后从数组中删除。
然后我们再从删除的的那个字符串所在的文件中,取出下一个字符串,重复上述操作,直到所有文件都合并完成。
这里每次都要比较所有的文件,然后找出最小值,每轮的比较遍历数组中所有的数据,时间复杂度差不多为O(n)
。
如果我们从小文件中取出的字符串放入小顶堆中,堆顶元素也就是优先级队列队首的元素,就是最小的字符串。我们把字符串放入大文件中,并从堆中删除。然后再从小文件中取出下一个字符串,放入堆中。循环这个过程,直到所有文件都合并完成。
我们每次只需要取出堆顶元素即可,这时候的操作就是删除堆顶元素和插入新的元素,而他们的时间复杂度都是O(logn)
高性能定时器
以前做定时任务,很多时候可能要每隔一段时间去扫描一遍任务,查看是否有任务要执行,只有何种做法很低效。
第一,任务的约定执行时间离当前时间可能还有很久,这样前面很多次扫描其实都是徒劳的;
第二,每次都要扫描整个任务列表,如果任务列表很大的话,会比较耗时。
可以使用优先级队列解决问题,按照任务设定的执行时间,将这些任务存储在优先级队列中,队列首部存储的是最先执行的任务。
我们用队首任务的执行时间点,与当前时间点相减,得到一个时间间隔 T,即当前时间开始,需要等待多久,才会有第一个任务需要执行,这样定时器就可以设定在 T 秒后,再来执行任务,这期间不需要做任何事情。
求 Top K 的数据
求数据中前 K 大的数据
我们首先创建一个容纳 K 个数据的小顶堆,堆顶数据是最小的那个,然后每新增一个数据,与堆顶元素进行比较,如果比堆顶元素小,则舍弃掉,如果比堆顶元素大,则删除堆顶元素,把这个元素插入到小顶堆中。重复上述操作,直到所有数据都比较完,堆中存放的数据一直是需要的数据。
创建堆的时候,堆化的时间复杂度为O(logn)
,堆的删除和添加元素的时间复杂度都是 O(logn)
,假设数组中的所有元素都要进行添加操作,那么整个操作的时间复杂度就是O(nlogn)
求中位数
求一组动态数据集合的中位数
假设有 n 个数字,如果我们把一组数字按照从小到大的顺序排好序,如果 n 是偶数,那么中位数就是第 n/2
和 n/2+1
个数据,我们可以从中选取一个。如果 n 是奇数,那么中位数就是第 n/2+1
个数据。
- 数据按照从小到大的顺序排列
- 创建两个堆,一个大顶堆和一个小顶堆,假如有偶数个数据,在大顶堆中存放前
n/2
个数据,如果有奇数个数据,就在大顶堆中存放前n/2+1
个数据, - 其余数据在小顶堆中存放,这时候大顶堆的堆顶数据就是中位数。
- 如果有新增数据,就与两个堆的堆顶数据进行对比,如果大于等于小顶堆的堆顶元素,就存放到小顶堆中,如果小于等于大顶堆的堆顶元素,就存放到大顶堆中。
- 此时很容易出现数量不匹配的情况, 不再满足第二步的条件,此时,我们只要数量多的一方的堆顶数据移动到数量少的一方即可。
- 然后调整堆中数据的大小关系,即可保持平衡。
堆化的时间复杂度为O(logn)
,中位数的获取时间复杂度为O(1)
同样的道理我们还可以求解任意比例的数据,比如快速求解接口的99%响应时间。