1.优先级队列介绍
1.1 优先级队列
有时在调度任务时,我们会想要先处理优先级更高的任务。例如,对于同一个柜台,在决定队列中下一个服务的用户时,总是倾向于优先服务VIP用户,而让普通用户等待,即使普通的用户是先加入队列的。
优先级队列和普通的先进先出FIFO的队列类似,最大的不同在于,优先级队列中优先级最高的元素总是最先出队的,而不是遵循先进先出的顺序。
1.2 堆
优先级队列的接口要求很简单。从逻辑上来说,向量、链表或者平衡二叉搜索树等数据结构都可用于实现优先级队列。但考虑到时间和空间的效率,就必须仔细斟酌和考量了。而一种被称为堆的数据结构非常适合实现优先级队列。’
堆和二叉搜索树类似,存储的元素在逻辑上是按照层次排放的,在全局任意地方其上层元素优先级大于下层元素,这一顺序性也被称为堆序性,而其中优先级最大的元素被放在最高的层级(大顶堆)。和二叉搜索树的排序方式不同的是,堆中元素的顺序并不是完全的排序,而只是维护了一种偏序关系,被称为堆序性。在这种偏序关系下,元素之间的顺序性比较疏散,维护堆序性的代价比较低,因而在实现优先级队列时,堆的效率要高于平衡二叉搜索树。
1.3 完全二叉堆
完全二叉堆是堆的一种,其元素在逻辑上是以完全二叉树的形式存放的,但实际却是存储在向量(数组)中的。在这里,我们使用完全二叉堆来实现优先级队列。
2.优先级队列ADT接口
/*** 优先级队列 ADT接口*/
public interface PriorityQueue {/*** 插入新数据
*@paramnewData 新数据
**/
voidinsert(E newData);/*** 获得优先级最大值(窥视) 不删数据
*@return当前优先级最大的数据
**/E peekMax();/*** 获得并且删除当前优先级最大值
*@return被删除的 当前优先级最大的数据*/E popMax();/*** 获得当前优先级队列 元素个数
*@return当前优先级队列 元素个数
**/
intsize();/*** 是否为空
*@returntrue 队列为空
* false 队列不为空
**/
booleanisEmpty();
}
3.完全二叉堆实现细节
3.1 基础属性
完全二叉堆内部使用之前封装好的向量作为基础。和二叉搜索树类似,用户同样可以通过传入Comparator比较器来指定堆中优先级大小比较的逻辑。
public class CompleteBinaryHeap implements PriorityQueue{/*** 内部向量
**/
private ArrayListinnerArrayList;/*** 比较逻辑
**/
private final Comparatorcomparator;/*** 当前堆的逻辑大小
**/
private intsize;
}
构造方法:
/*** 无参构造函数
**/
publicCompleteBinaryHeap() {this.innerArrayList = new ArrayList<>();this.comparator = null;
}/*** 指定初始容量的构造函数
*@paramdefaultCapacity 指定的初始容量
**/
public CompleteBinaryHeap(intdefaultCapacity){this.innerArrayList = new ArrayList<>(defaultCapacity);this.comparator = null;
}/*** 指定初始容量的构造函数
*@paramcomparator 指定的比较器逻辑
**/
public CompleteBinaryHeap(Comparatorcomparator){this.innerArrayList = new ArrayList<>();this.comparator =comparator;
}/*** 指定初始容量和比较器的构造函数
*@paramdefaultCapacity 指定的初始容量
*@paramcomparator 指定的比较器逻辑
**/
public CompleteBinaryHeap(int defaultCapacity, Comparatorcomparator) {this.innerArrayList = new ArrayList<>(defaultCapacity);this.comparator =comparator;
}/*** 将指定数组 转换为一个完全二叉堆
*@paramarray 指定的数组
**/
publicCompleteBinaryHeap(E[] array){this.innerArrayList = new ArrayList<>(array);this.comparator = null;this.size =array.length;//批量建堆
heapify();
}/*** 将指定数组 转换为一个完全二叉堆
*@paramarray 指定的数组
*@paramcomparator 指定的比较器逻辑
**/
public CompleteBinaryHeap(E[] array, Comparatorcomparator){this.innerArrayList = new ArrayList<>(array);this.comparator =comparator;this.size =array.length;//批量建堆
heapify();
}
3.2 辅助方法
由于完全二叉堆在逻辑上等价于一颗完全二叉树,但实际上却采用了一维的向量数据结构来存储元素。因而我们需要实现诸如getParentIndex、getLeftChildIndex、getRightChildIndex等方法来进行完全二叉树和向量表示方法的转换。
这里,定义了一些私有方法来封装常用的逻辑,用以简化代码。
/*** 获得逻辑上 双亲节点下标
*@paramcurrentIndex 当前下标
**/
private int getParentIndex(intcurrentIndex){return (currentIndex - 1)/2;
}/*** 获得逻辑上 左孩子节点下标
*@paramcurrentIndex 当前下标
**/
private int getLeftChildIndex(intcurrentIndex){return (currentIndex * 2) + 1;
}/*** 获得逻辑上 右孩子节点下标
*@paramcurrentIndex 当前下标
**/
private int getRightChildIndex(intcurrentIndex){return (currentInd