目录
[1. 优先级队列](#1. 优先级队列)
[1.1 概念](#1.1 概念)
[2. 优先级队列的模拟实现](#2. 优先级队列的模拟实现)
[2.1 堆的概念](#2.1 堆的概念)
[2.2 堆的性质](#2.2 堆的性质)
[2.3 堆的两种结构](#2.3 堆的两种结构)
[2.4 堆的存储方式](#2.4 堆的存储方式)
[2.5 堆的创建](#2.5 堆的创建)
[2.5.1 堆向下调整](#2.5.1 堆向下调整)
[2.5.2 堆的创建](#2.5.2 堆的创建)
[2.6 堆的插入与删除](#2.6 堆的插入与删除)
[2.6.1 堆的插入](#2.6.1 堆的插入)
[2.6.2 堆的删除](#2.6.2 堆的删除)
[3.1 PriorityQueue的特性](#3.1 PriorityQueue的特性)
[3.2 PriorityQueue常用接口介绍](#3.2 PriorityQueue常用接口介绍)
[1. 优先级队列的构造](#1. 优先级队列的构造)
[2. 插入/删除/获取优先级最高的元素](#2. 插入/删除/获取优先级最高的元素)
[4. 堆的应用](#4. 堆的应用)
[4.1 PriorityQueue的实现](#4.1 PriorityQueue的实现)
[4.2 堆排序](#4.2 堆排序)
[1. 建堆](#1. 建堆)
[2. 利用堆删除思想来进行排序](#2. 利用堆删除思想来进行排序)
**1.**优先级队列
1.1****概念
队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列 ,使用队列显然不合适,比如:排座位时可能会让成绩好的同学先挑座位。 在这种情况下, 数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue) 。
**2.**优先级队列的模拟实现
2.1****堆的概念
如果有一个 关键码的集合 K = {k0 , k1 , k2 , ... , kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储在一 个一维数组中 ,并满足: Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0 , 1 , 2... ,则 称为小堆 ( 或大堆) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2.2 堆的性质
1.堆中某个节点的值总是不大于或不小于其父节点的值(在最大堆中,任意一个节点的值都不会大于它的父节点的值,在最小堆中,任意一个节点的值都不会小于它的父节点的值 )。
2.堆总是一棵完全二叉树。
2.3 堆的两种结构
2.4 堆的存储方式
从堆的概念可知, 堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储
注意:对于 非完全二叉树,则不适合使用顺序方式进行存储 ,因为为了能够还原二叉树, 空间中必须要存储空节 点,就会导致空间利用率比较低 。
将元素存储到数组中后,可以根据二叉树章节的性质 5 对树进行还原。假设 i 为节点在数组中的下标,则有:
·如果 i 为 0 ,则 i 表示的节点为根节点,否则 i 节点的双亲节点为 (i - 1)/2
·如果 2 * i + 1 小于节点个数,则节点 i 的左孩子下标为 2 * i + 1 ,否则没有左孩子
·如果 2 * i + 2 小于节点个数,则节点 i 的右孩子下标为 2 * i + 2 ,否则没有右孩子
2.5****堆的创建
2.5.1****堆向下调整
对于集合 { 27,15,19,18,28,34,65,49,25,37 } 中的数据,如果将其创建成堆呢?
仔细观察上图后发现: 根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可 。
向下过程 ( 以小堆为例 ) :
-
让 parent 标记需要调整的节点, child 标记 parent 的左孩子 ( 注意: parent 如果有孩子一定先是有左孩子 )
-
如果 parent 的左孩子存在,即 :child < size ,进行以下操作,直到 parent 的左孩子不存在
·parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标
·将 parent 与较小的孩子 child 比较,如果:public void shiftDown(int parent, int usedSize) {
int child = (2 * parent) + 1;
while (child < usedSize) {
//先定义再进行这样的判断,可以有效避免数组越界的问题
int rightChild = child + 1;
// 确保右孩子节点存在且不越界
if (rightChild < usedSize && elem[rightChild] > elem[child]) {
child++;
}
if (elem[child] > elem[parent]) {
swap(child, parent);
parent = child;
child = (2 * parent) + 1;
} else {
break;
}
}
}public void swap(int i,int j){ int tmp = elem[i]; elem[i] = elem[j]; elem[j] = tmp; }
代码
public void shiftDown(int parent, int usedSize) {
int child = (2 * parent) + 1;
while (child < usedSize) {
//先定义再进行这样的判断,可以有效避免数组越界的问题
int rightChild = child + 1;
// 确保右孩子节点存在且不越界
if (rightChild < usedSize && elem[rightChild] > elem[child]) {
child++;
}
if (elem[child] > elem[parent]) {
swap(child, parent);
parent = child;
child = (2 * parent) + 1;
} else {
break;
}
}
}
public void swap(int i,int j){
int tmp = elem[i];
elem[i] = elem[j];
elem[j] = tmp;
}
2.5.2 堆的创建
public void createHeap(){
for (int parent = (UsedSize-1-1) / 2; parent >=0 ; parent--) {
//自己定义一个停止标志
shiftDown(parent,UsedSize);
}
}
2.6****堆的插入与删除
2.6.1****堆的插入
- 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
- 将最后新插入的节点向上调整,直到满足堆的性质
代码
public void offer(int val){
//判断当前数组是满的吗?
//1.不满就放在最后一个位置
//2.满就扩容
if(isFull()){
this.elem = Arrays.copyOf(elem,2*elem.length);
}
//扩容完毕之后将插入的数据放进去
this.elem[UsedSize] = val;
//直接从插入的位置开始判断大根堆
shiftUp(UsedSize);
//加入新的元素要将计数器更新
UsedSize++;
}
public boolean isFull(){
return UsedSize == elem.length;
}
2.6.2 堆的删除
- 将堆顶元素对堆中最后一个元素交换
- 将堆中有效数据个数减少一个
- 对堆顶元素进行向下调整
代码
public int poll(){
int tmp = elem[0];
swap(0,UsedSize-1);
UsedSize--;
shiftDown(0,UsedSize);
return tmp;
}
public void shiftDown(int parent, int usedSize) {
int child = (2 * parent) + 1;
while (child < usedSize) {
//先定义再进行这样的判断,可以有效避免数组越界的问题
int rightChild = child + 1;
// 确保右孩子节点存在且不越界
if (rightChild < usedSize && elem[rightChild] > elem[child]) {
child++;
}
if (elem[child] > elem[parent]) {
swap(child, parent);
parent = child;
child = (2 * parent) + 1;
} else {
break;
}
}
}
**3.**常用接口介绍
3.1 PriorityQueue****的特性
Java 集合框架中提供了 PriorityQueue 和 PriorityBlockingQueue 两种类型的优先级队列, PriorityQueue 是线
程不安全的, PriorityBlockingQueue 是线程安全的 ,本文主要介绍 PriorityQueue 。
注意:
-
使用时必须导入PriorityQueue所在的包,即:
importjava.util.PriorityQueue;
-
PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常
-
不能插入null对象,否则会抛出****NullPointerException
-
没有容量限制,可以插入任意多个元素,其内部可以自动扩容
-
PriorityQueue 底层使用了堆数据结构
-
PriorityQueue 默认情况下是小堆 --- 即每次获取到的元素都是最小的元素
3.2 PriorityQueue****常用接口介绍
1. 优先级队列的构造
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队列是小堆,如果需要大堆需要用户提供比较器
// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
p.offer(4);
p.offer(3);
p.offer(2);
p.offer(1);
p.offer(5);
System.out.println(p.peek());
}
}
此时创建出来的就是一个大堆
2. 插入**/删除/**获取优先级最高的元素
static void TestPriorityQueue2(){
int[] arr = {4,1,9,2,8,0,7,3,6,5};
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
for (int e: arr) {
q.offer(e);
}
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
// 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
q.poll();
q.poll();
System.out.println(q.size()); // 打印优先级队列中有效元素个数
System.out.println(q.peek()); // 获取优先级最高的元素
q.offer(0);
System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
q.clear();
if(q.isEmpty()){
System.out.println("优先级队列已经为空!!!");
}
else{
System.out.println("优先级队列不为空");
}
}
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity
优先级队列的扩容说明:
`如果容量小于 64 时,是按照 oldCapacity 的 2 倍方式扩容的
`如果容量大于等于 64 ,是按照 oldCapacity 的 1.5 倍方式扩容的
`如果容量超过 MAX_ARRAY_SIZE ,按照 MAX_ARRAY_SIZE 来进行扩容
**4.**堆的应用
4.1 PriorityQueue****的实现
用堆作为底层结构 封装优先级队列
4.2****堆排序
堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆 shiftup()
降序:建小堆 shiftdown()
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。
希望可以帮到读者