优先队列
wikipedia
优先队列是计算机科学中的一类抽象数据类型。优先队列中的每个元素都有各自的优先级,优先级最高的元素最先得到服务;优先级相同的元素按照其在优先队列中的顺序得到服务。优先队列往往用堆来实现。
普通队列:先进先出;后进后出
优先队列:出队顺序和入队顺序无关;和优先级相关
使用场景
- 即时战略游戏,自动打怪,怪也是源源不断的增加
- 医生治疗优先级病人,动态的,病人会远远不断的增加
优先级强调的是动态的过程,数据是不断新增的
实现
初级实现
有许多简单低效的实现。如用一个有序的数组;或使用无序数组,在每次取出时搜索全集合,这种方法插入的效率为O(1),但取出时效率为O(n)。
典型实现
出于性能考虑,优先队列用堆)来实现,具有O(log n)时间复杂度的插入元素性能,O(n)的初始化构造的时间复杂度。如果使用自平衡二叉查找树,插入与删除的时间复杂度为O(log n),构造二叉树的时间复杂度为O(n log n)。
从计算复杂度的角度,优先级队列等价于排序算法。
有一些特殊的堆)为优先队列的实现提供了额外的性能:二叉堆的插入与提取操作的时间复杂度为O(log n),并可以常量时间复杂度的peek操作。二项堆提供了几种额外操作。斐波那契堆的插入、提取、修改元素优先级等操作具有分摊常量时间复杂度,[1],但删除操作的时间复杂度为O(log n)。Brodal queue具有最糟糕情况下的常量复杂度但算法相当复杂因而不具有实用性。
对于整型、浮点型等具有有限值域的元素的数据类型,优先队列有更快的实现。
入队 | 出队(拿出最大元素) | |
---|---|---|
普通线性结构 | $O(1)$ | $O(n)$ |
顺序线性结构 | $O(n)$ | $O(1)$ |
堆 | $O(logn)$ | $O(logn)$ |
堆
wikipedia
堆(英语:Heap)是计算机科学中的一种特别的树状数据结构。若是满足以下特性,即可称为堆:“给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值”。若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。
堆始于J. W. J. Williams在1964年发表的堆排序(heap sort),当时他提出了二叉堆树作为此算法的数据结构。堆在戴克斯特拉算法(英语:Dijkstra’s algorithm)中亦为重要的关键。
在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。[1]
二叉堆的特性
二叉堆是一颗完全二叉树
二叉堆的性质(Binary Heap)
- 堆中某个节点的值总是不大于其父节点的值
- 最大堆(相应的可以定义做小堆)
存储二叉树的方式
二叉树 顺序存储表示的话,可以用数组或链接串列来存储,由于是满二叉树就能紧凑排列而不浪费空间
1 | 如果1标记为根节点 |
支持的基本操作
- 创建空堆
- 向堆中添加元素和 Sift up
- 向堆中取出最大元素和 Sift down
- heapify 和 replace
add 和 extractMax 时间复杂度都是 $O(logn)$
replace:取出最大元素后,放入一个新元素
实现:可以先 extractMax,再 add,再次 $O(logn)$ 的操作
实现:可以直接将堆顶元素替换以后 Sift Down,一次 $O(logn)$ 的操作
heapify:将任意数组整理成堆的形状
Heapify 的算法复杂度
将 n 个元素逐个插入到一个空堆中,算法复杂度是 $0(nlogn)$
heapify 的过程,算法复杂度为 $0(n)$
MaxHeap 的具体实现
1 | public class MaxHeap<E extends Comparable<E>> { |
PriorityQueue 的具体实现
1 | public class PriorityQueue<E extends Comparable<E>> implements Queue<E> { |
案例
案例1:在 1 000 000 个元素中选出前 100 名
在 N 个元素中选出前 M 个元素
排序(归并排序,快速排序)?$NlogN$,
优先队列?$NlogM$
使用优先队列,维护当前看到的前 M 个元素
需要使用最小堆
1 | 1. 直接创建 1_000_000 个元素的数组 heapify「堆化」,然后通过 PriorityQueue.deQueue() 前 100 个元素 |
案例2:347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
1 | 1. 最终返回的是具体的元素,元素的优先级是通过出现频率来判定的 |
Solution1(非静态成员类封装 e, freq)
1 | import java.util.LinkedList; |
Solution2(PriorityQueue -》java.utils.java.util.PriorityQueue)
1 | import java.util.*; |
Solution3(Comparator 形式代替 Comparable)
1 | import java.util.*; |
Solution4(Comparator 匿名类代替非静态成员类)
1 | import java.util.PriorityQueue; |
Solution5(lambda 简化代码)
1 | import java.util.PriorityQueue; |