概念+例子
队列是一种先进先出的数据结构,但有时操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列。这种情况下,数据结构应该提出两个最基本的操作:一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)
JDK:提供PriorityQueue接口使用
Ⅰ. 注意:插入的元素不能为null,且元素之间必须要能够进行比较
Ⅱ.插入||删除→在插入和删除元素期间,优先级队列中的元素会自定进行调整 时间复杂度:O(log2N) 发现:默认情况下,不论怎么调整,0号位置的元素始终是最小的
Ⅲ.底层结构→堆(new出的对象在堆上,优先级队列底层结构也是堆,此处的两个堆不是同一个概念)
new→堆:指的是一块具有特殊作用的内存空间
优先级队列底层的堆:指的是一种数据结构
PriorityQueue中包含的接口
构造方式:PriorityQueue()空的优先级队列→默认的容量:11
PriorityQueue(capacity):指定初始化容量大小→注意:capacity不能小于1,如果小于1,抛异常
PriorityQueue(集合对象):用一个集合中的元素来构造优先级队列
》Offer(x):插入元素X→Olog(N)
》poll()→删除元素,每次都是将最小的元素删除了
》peek()→取最小的元素
》size()→获取有效元素个数
》isEmpty()→检查是否为空
》clear()→将优先级队列中的元素清空
Top-K问题
Top-K问题:前K个最大或者最小的元素
例:给一非空的单词列表,返回前k个出现次数最多的单词
大概思路:1.统计每个单词出现的次数
2.借助优先级队列获取出现次数最多的前K个单词
堆的概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆中的元素:将其按照完全二叉树的方式存储在数组中(将完全二叉树按照排序的方式进行存储)
任意节点:比其孩子节点都小→小堆
任意节点:比其孩子节点都大→大堆
堆的特性:堆顶元素一定是堆中所有元素的最大值(大堆)或者最小值(小堆)
从堆顶到其叶子每条路径都是降序(大堆)或者升序(小堆)→堆序
堆的存储方式
堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储
注意:对于**非完全二叉树,则不适合顺序的方式进行存储。**因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。
将元素存储到数组中后,可以根据二叉树的性质对树进行还原。假设i为节点在数组中的下标,则有:
Ⅰ.如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
Ⅱ.如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
Ⅲ.如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子
例:堆的创建
对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
画图后发现:根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可
向下过程(以小堆为例):
- 让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)
- 如果parent的左孩子存在,即:child < size, 进行以下操作,直到parent的左孩子不存在
.parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child标记
.将parent与较小的孩子child比较,如果:
Ⅰ.parent小于较小的孩子child,调整结束
Ⅱ.否则,交换parent与较小的孩子child,交换完之后,parent中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent=child;child=parent*2+1
注意:在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。
时间复杂度分析:
最坏的情况为从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(log2N)
import java.util.PriorityQueue;
public class MyPriorityQueue {
private int[] array;
public MyPriorityQueue() {
//默认的构造----将其底层容量设置为11
array = new int[11];
}
public MyPriorityQueue(int initCapacity) {
if (initCapacity < 1) {
//标准库:抛出一个非法参数的异常
initCapacity = 11;
}
array = new int[initCapacity];
}
//注意:标准库中没有该接口---标准库中可以采用集合来构造优先级数列
public MyPriorityQueue(int[] arr) {
array = new int[arr.length];
for (int i = 0; i < arr.length; ++i) {
array[i] = arr[i];
}
//将array中的元素调整,让其满足小堆的性质
//找倒数第一个非叶子节点
int lastLeaf = (array.length - 2) >> 1;
for (int root = lastLeaf; root >= 0; root--) {
shiftdown(root);
}
}
//parent表示本次需要调整的节点的小标
//调整以parent为根的二叉树
//注意:调整之前,一定要保证parent的左右子树已经满足小堆的性质
//如果要检测parent是否满足小堆的性质,只需要使用parent与其孩子进行比较
//满足小堆性质→说明以parent为根的二叉树已经是小堆
//不满足小堆性质→说明parent比起孩子大,此时需要将parent与其较小的孩子进行交换
//交换完以后,parent较大的元素向下移动,可能导致其子树不满足小堆的性质
//需要继续调整其子树
private void shiftdown(int parent) {
//使用child标记parent的较小的孩子
//默认情况下先让其标记左孩子,因为parent可能有左孩子但是没有右孩子
int child = parent * 2 + 1;
int size=array.length;
while (child < size) {
//找parent中较小的孩子
//在用左右孩子比较时,必须要保证右孩子存在---while循环条件已经保证左孩子存在
if (child + 1 < size && array[child + 1] < array[child]) {
child += 1;
}
//检测较小的孩子是否比双亲小
if (array[child] < array[parent]) {
//说明parent不满足小堆的性质--将parent和child交换
swap(parent, child);
//parent较大的元素向下移动可能会导致子树不满足堆的性质
//继续向下调整
parent = child;
child = parent * 2 + 1;
} else {
//以parent为根的二叉树已经满足堆的性质
return;
}
}
}
private void swap(int parent, int child) {
int temp = array[parent];
array[parent] = array[child];
array[child] = temp;
}
public static void main(String[] args) {
int array[] = {5, 3, 7, 1, 4, 6, 2};
MyPriorityQueue mp = new MyPriorityQueue(array);
}
}