文章目录
一、什么是优先级队列(堆)
普通队列:按照元素的入队顺序出队,先入先出。
优先级队列:按照优先级的大小动态出队(动态指的是元素个数动态变化,而非固定)。
现实生活中的优先级队列:
医生根据病人病情的情况对手术排期
病情相同的情况下按来的先后顺序,若病情较重优先安排手术
操作系统的任务调度
优先级队列,系统的任务一般优先级都比普通应用高
优先级队列的数据是在动态变化的。
时间复杂度对比:
入队 | 出队 | |
---|---|---|
普通的链式队列 | O(1) | O(n) |
优先级队列 | O(log n) | O(log n) |
在计算机领域,若见到log n时间复杂度,近乎一定和“树”结构有关
二、基于二叉树的堆(二叉堆)
堆有很多种(d叉堆、索引堆…),二叉堆是应用最广泛的堆。
1.二叉堆的特点:
-
是一棵完全二叉树,基于数组存储(元素都是靠左排列,数组中存储不会浪费空间)
只有完全二叉树适合使用数组这种结构来存储。其他二叉树都要用链式结构。
-
关于节点值:
堆中根节点值 >= 子树节点中的值(最大堆、大根堆)
堆中根节点值 <= 子树节点中的值(最小堆、小根堆)
JDK中的PriorityQueue默认是给予最小堆的实现
①.完全二叉树
②.所有子树都满足
根 >= 子树 最大堆
根 <= 子树 最大堆
-
关于节点的编号:
因为堆是基于数组来存储的,节点之间的关系通过数组下标来表示,从0开始编号,数组下标也是从0开始
假设此时节点编号为i,且存在父节点
父节点编号 parent = (i - 1) / 2;
左子树编号 left = 2 * i + 1;
右子树编号 right = 2 * i + 2;
2. 最大堆的实现
向堆中添加元素
-
数组添加元素尾插,在数组末尾添加元素——>此时仍是一棵完全二叉树,节点紧密排列;
-
添加元素后,可能会破坏最大堆的定义,因此进行元素上浮操作siftUp,直到把当前元素上浮到合适位置。
/** * 上浮操作,让新添加的元素去到它该去的位置 * @param k */ private void siftUp(int k) { while (k > 0 && elementData.get(k) > elementData.get(parent(k))) { swap(k , parent(k)); k = parent(k); } }
在堆中取出最大值(最大堆)
-
当前最大堆的最大值就在树根节点,直接取出就可;
-
将堆中最后一个元素顶到堆顶(覆盖掉最大节点),然后进行元素下沉操作siftDown使其仍满足最大堆性质。
/** * 从索引k开始进行下沉操作 * @param k */ private void siftDown(int k) { //还存在子树 while (leftChid(k) < size){ int j = leftChid(k); //此时还存在右子树 if (j + 1 < size && elementData.get(j + 1) > elementData.get(j)) { //此时右子树的值大于左子树 j ++; } //此时j一定对应了左右子树最大值索引 if (elementData.get(k) >= elementData.get(j)) { //当前元素大于左右子树最大值,下沉结束,元素k落在了最终位置 break; }else { swap(k,j); k = j; } } }
heapify——堆化
任意一个整形数组都可以看做一个完全二叉树,只需要进行元素调整操作。
方法:
-
将这n个元素依次调用add方法添加到一个新的最大堆中
时间复杂度:O(n logn )
空间复杂度:O(n)
-
原地堆化 ——> O(n)级别时间复杂度
从最后一个非叶子节点开始进行元素siftDown操作。从当前二叉树中最后一个小子树开始调整,不断向前,直到调整到根节点
public MaxHeap(int[] arr) { elementData = new ArrayList<>(arr.length); //1.先将所有元素复制到data数组中 for (int i : arr) { elementData.add