PriorityQueue(优先级队列)
一.概念
队列是一种先进先出的结构,在一些特殊的情况下,我们需要队列返回的可能不只是第一个那么简单,而是队列中最大或者最小的,这就需要用到优先级队列了。
优先级队列这种数据结构能够提供:返回最高优先级对象,添加新对象两种方法。
二.模拟实现
在JDK1.8PriorityQueue底层使用了堆这种数据结构,实现堆实际上就是实现优先级队列,而堆又是在完全二叉树的基础上进行了一些调整。
2.1.堆的概念
堆其实就是将二叉树的每一个树的根节点都设为当前最大/最小,我们称之为小堆/大堆。
2.2.堆的性质
堆中某个结点的值总是不大于或不小于其父节点的值;
堆总是一颗完全二叉树
2.3.堆的存储方式
从堆的概念来看可以用层序的规则来进行存储
2.4.堆的创建
2.4.1堆的向下调整
顾名思义向下调整就是往下调整,根据什么规则呢?这要看是要创建大堆还是小堆,我们以创建大堆来进行举例
我们直接来上手代码
private void shiftDown(int parent, int useSize) {int child = (2 * parent) + 1;while(child < useSize){if( child + 1 < useSize && elem[child] < elem[child + 1] ){child++;}if(elem[child] > elem[parent]){swap(child,parent);parent = child;child = 2* parent + 1;}else{break;}}}
2.4.2向上调整(大根堆)
具体跟小根堆一样,一个向下一个向上。
private void shiftup(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;}}}
3.堆的创建(大根堆)
public void creatHeap(){for (int parent = (useSize - 1 - 1) / 2; parent >= 0 ; parent--) {shiftDown(parent,useSize);}}
3.1堆的插入
堆的插入就是将要插入的元素放到最后一个有效元素的后面,要提前进行空间检查,然后进行向上调整,因为只有一条分支没有进行调整所以不用在进行递归。
public void offer(int val){if(isFull()){this.elem = Arrays.copyOf(elem,2*elem.length);}this.elem[useSize] = val;shiftup(useSize);useSize++;}
3.2堆的删除
堆的删除实际上就是将最后一个元素和将要删除的元素进行调换,然后进行向下调整,这里在向下调整是我们是不会堆最后一个元素进行向下调整的,所以这个元素就相当于删除了。
public int poll(){int tmp = elem[0];swap(0,useSize - 1);useSize--;shiftDown(0,useSize);return tmp;}
说了这么多,我们终于可以模拟实现优先级队列了。
public class TestHeap {private int[] elem;private int useSize;public TestHeap(){this.elem = new int[10];}public void initHeap(int[] array){for (int i = 0; i < array.length; i++) {elem[i] = array[i];useSize++;}}public void creatHeap(){for (int parent = (useSize - 1 - 1) / 2; parent >= 0 ; parent--) {shiftDown(parent,useSize);}}private void shiftDown(int parent, int useSize) {int child = (2 * parent) + 1;while(child < useSize){if( child + 1 < useSize && elem[child] < elem[child + 1] ){child++;}if(elem[child] > elem[parent]){swap(child,parent);parent = child;child = 2* parent + 1;}else{break;}}}private void swap(int i, int j){int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}public void offer(int val){if(isFull()){this.elem = Arrays.copyOf(elem,2*elem.length);}this.elem[useSize] = val;shiftup(useSize);useSize++;}private void shiftup(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;}}}public boolean isFull(){return useSize == elem.length;}public int poll(){int tmp = elem[0];swap(0,useSize - 1);useSize--;shiftDown(0,useSize);return tmp;}
三.priorityqueue特性
1. 使用时必须得导入包
package priorityQueue;
2.队列中放置的元素必须的得是能够比较大小的,否则会抛出异常
3.不能插入null对象,否则会抛出空指针异常
4.没有容量限制,其内部可以自动扩容
5.插入和删除元素的时间复杂度为O(logN)
6,队列底层使用了堆数据结构
7.队列默认情况下是小堆数据结构
如果要改成大堆结构,需要提供比较器
class IntCmp implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);}
}
我们就以最小k个值来举例
就是在一堆数据中找到前k个最小的值,我们知道大堆的根结点都是最大的,所以我们可以找到一堆最小的数当中的最大值,我们可以先把k个值放入优先级队列(大堆结构),然后再将k后的这些值于优先级队列最大值进行比较,比根节点小,就交换。
public static int[] bigestk(int[] array, int k){int[] ret = new int[k];if(array == null || k <= 0){return ret;}PriorityQueue<Integer> priorityQueue =new PriorityQueue<>(new IntCmp());for (int i = 0; i < k; i++) {priorityQueue.offer(array[i]);}for (int i = k; i < array.length ;i++) {int top = priorityQueue.peek();if(array[i] < top){priorityQueue.poll();priorityQueue.offer(array[i]);}}for (int i = 0; i < k; i++) {ret[i] = priorityQueue.poll();}return ret;}
我们再这里就提供了一个比较器。