提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:以下是本篇文章正文内容,下面案例可供参考
一、优先级队列
1、概念
队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列;数据结构应该提供两个最基本的操作:1) 返回最高优先级对象,2) 是添加新的对象。这种数
据结构就是优先级队列(Priority Queue)。
2、堆的概念
JDK1.8中的PriorityQueue底层使用了堆这种数据结构
如果有一个关键码的集合合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >=K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
1)性质
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
2)堆的存储方式
由堆的概念可知,堆是一棵完全二叉树,可以层序的规则采用顺序的方式来高效存储。
二、堆的创建及相关操作
1、堆向下调整
以小堆为例(根节点的左右子树已经满足)
过程:
操作1:parent标记需要调整的节点,child标记parent的左孩子.
操作2:如果,child < size,则进行以下过程
(1) 若右孩子存在,存在找到左右孩子中最小的孩子,用child进行标识;
(2) 将parent与较小的孩子child比较;如果:parent小于较小的孩子child,调整结束;否则:交换parent与较小的孩子child,交换完成之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child;child = parent*2+1; 然后继续操作2。直到 child >size。
最坏的情况:从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(log2(N))
向下调整相关代码
/**
* 向下调整
* 如果向下调整大根堆,时间复杂度为O(n)
* @param parent
* @param len
*/
private void siftDown(int parent,int len){
int child = 2*parent+1;
//至少有左孩子
while (child<len){
//左孩子和右孩子比较,如果如果右孩子的值比较大,则交换
if (child+1< len && elem[child]<elem[child+1]){
child = child +1;
}
//走完上述if语句,child一定保存的是左右孩子最大值的下标
if (elem[child]>elem[parent]){
swap(child,parent);
parent = child;
child = parent*2+1;
}else{
break;
}
}
}
2、创建堆
创建堆的时间复杂度为T(N) = N -log2(N) = N
[注意:假设树的高度为h,第一层的节点最坏移动h-1层,每层节点的最坏移动层数相加即可得到时间复杂度]
public void CreateHeap(){
//节点下标i=len-1 ;父母节点下标为(i-1)/2
for (int parent = (usedSize-1-1)/2; parent >=0 ; parent--) {
siftDown(parent,usedSize);
}
}
3、堆的插入
堆的插入共两个步骤:
- 先将元素放入到底层空间中(注意:空间不够时需要扩容)
- 将最后新插入的节点向上调整,直到满足堆的性质
堆的插入相关代码
//判断是否为满
public boolean isFull(){
return usedSize == elem.length;
}
public void push(int val){
if (isFull()){
elem = Arrays.copyOf(elem,2*elem.length);
}
elem[usedSize] = val;
//向上调整
siftUp(usedSize);
usedSize++;
}
//向上调整建立小根堆,时间复杂度O(N+NlogN)
public void siftUp(int child){
int parent = (child-1)/2;
while (child >0){
if (elem[child] >elem[parent]){
swap(child,parent);
child = parent;
parent = (child-1)/2;
}else {
break;
}
}
}
4、堆的删除
堆的删除一定删除的是堆顶元素。具体如下:
- 将堆顶元素对堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整
相关代码
//删除
public int pop(){
if (isEmpty()){
return -1;
}
int oldVal = elem[0];
swap(0,usedSize-1); //将第一个元素与最后一个元素交换
usedSize--;
siftDown(0,usedSize);
return oldVal;
}
public boolean isEmpty(){
return usedSize == 0;
}
三、PriorityQueue的特性
static void TestPriorityQueue(){
// 创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
ArrayList<Integer> list = new ArrayList<>();
list.add(4);
list.add(3);
list.add(2);
list.add(1);
// 用ArrayList对象来构造一个优先级队列的对象
// q3中已经包含了三个元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
System.out.println(q3.size());
//获取有效元素的个数
System.out.println(q3.peek());
//获取优先级最高的元素
}
PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器,即可调整为大根堆