一、优先级概念
什么是优先级:比如女士优先,个子低的优先排到前面去,有一部分数据具备优先级,要以优先级的顺序将顺序存储起来。
前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列
priorityQueue是二叉树的完全二叉树,只不过是用数组来进行顺序存储
PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。
二、堆的概念
所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki 且 Ki= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
小根堆:根节点的大小小于孩子节点
大根堆:根节点的大小大于孩子节点
三、堆的存储方式
从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储,
完全二叉树:数组的每一个位置被充分使用
一般二叉树:有的位置被浪费掉没有存储值
所以对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。
四、堆的创建与方法实现
import java.util.Arrays;public class TestHeap {public int[] elem;public int usedSize;public TestHeap(){this.elem = new int[10];}public void init(int[] array){for(int i = 0;i<array.length;i++){elem[i] = array[i];usedSize++;}}//把elem数组当中的数据 调整为大根堆public void createHeap(){int size = usedSize;for(int parent = (size-1-1)/2;parent>=0;parent--){siftDown(parent,size);}}private void swap(int i,int j){int tmp = elem[i];elem[i] = elem[j];elem[j] = tmp;}//每棵子树向下调整 知父亲求孩子public void siftDown(int parent,int end){int child = 2*parent+1;while(child<end){if(child+1<end&&elem[child]<elem[child+1]){//找到大的 child++;}//chile下标就是左右孩子最大值if(elem[child]>elem[parent]){swap(child,parent);parent = child;child = 2*parent+1;}else{break;}}}//调整为小根堆public void siftDown2(int parent,int end){int child = 2*parent+1;while(child<end){if(child+1<end&&elem[child]>elem[child+1]){//找出最小child++;}//chile下标就是左右孩子最大值if(elem[child]<elem[parent]){swap(child,parent);parent = child;child = 2*parent+1;}else{break;}}}//插入public void offer(int val){if(isFull()){elem = Arrays.copyOf(elem,2*elem.length);}elem[usedSize] = val;usedSize++;siftUp(usedSize-1);}//向上调整成大根堆,知孩子求父亲public void siftUp(int child){int parent = (child-1)/2;while(parent>=0){if(elem[child]>elem[parent]){swap(child,parent);child = parent;parent = (child-1)/2;}else{break;}}}//向上调整复杂度O(N*logN)public boolean isFull(){return usedSize==elem.length;}//删除第一个节点 将0下标与最后一个节点交换,再向下调整public int poll(){if(isEmpty()){return -1;}int old = elem[0];swap(0,usedSize-1);usedSize--;siftDown(0,usedSize);return old;}public boolean isEmpty(){return usedSize==0;}//数组排序,从小往大排,建立大根堆,从大往小排建立小根堆public void heapSort(){int endIndex = usedSize-1;//O(N)while(endIndex>0){swap(0,endIndex);siftDown(0,endIndex);//调整成大根堆O(logN)endIndex--;}//O(N*logN)}}
对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
调整大根堆方法:从整棵树的最后一棵子树开始调整,每次让根节点与左右孩子比较,如果根节点比左右孩子的最大值都小,就交换根节点和孩子最大值。(小根堆同理)
上图就是大根堆
向下调整时间复杂度:约为N
Tn = 2^0*(h-1)+2^1*(h-2)+...+2^(h-2)*1+2^(h-1)*0
2Tn = 2^1*(h-1)+2^2*(h-2)+...+2^(h-1)*1
两式错位相减Tn = -2^0(h-1)+2^1+2^2+.....+2^(h-1)=-h-1+2^h
h = log以2为底(N+1)
Tn = -log(N+1)-1+N+1 = N-log(N+1)
n越来越大Tn约等于N
向上调整时间复杂度为N*logN
五、PriorityQueue常用接口介绍
1、优先级队列的构造(无构造器)
构造器 | 功能介绍 |
PriorityQueue() | 创建一个空的优先级队列,默认容量是11 |
PriorityQueue(int initialCapacity) | 创建一个初始容量为initialCapacity的优先级队列,注意: initialCapacity不能小于1,否则会抛IllegalArgumentException异 常 |
PriorityQueue(Collection<? extends E> c) | 用一个集合来创建优先级队列 |
public static void main(String[] args) {//堆是队列实现的,叫优先级队列,数组实现PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();priorityQueue.offer(12);priorityQueue.offer(5);priorityQueue.offer(57);System.out.println(priorityQueue.peek());//结果是5 所以默认是小根堆,也可以改为大根堆,以后讲Queue<Integer> queue = new PriorityQueue<>();//PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的PriorityBlockingQueue<Integer> priorityBlockingQueue = new PriorityBlockingQueue<>();//多线程下推荐使用}
2、 功能
函数名 | 功能介绍 |
boolean offer(E e) | 插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度,注意:空间不够时候会进行扩容 |
E peek() | 获取优先级最高的元素,如果优先级队列为空,返回null |
E poll() | 移除优先级最高的元素并返回,如果优先级队列为空,返回null |
int size() | 获取有效元素的个数 |
void clear() | 清空 |
boolean isEmpty() | 检测优先级队列是否为空,空返回true |
offer功能
给比较器
未给比较器
将key强转为Comparable,所以需要实现Comparable接口,未实现就会出现类型转换异常
比如:如果创建student类的堆,如果未实现接口就会抛出异常
compareTo可以实现大堆小堆的转换
比较器
默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器
提供比较器构造 | 功能介绍 |
public PriorityQueue(Comparator<? super E> comparator) | 可以改为大堆创建 |
PriorityQueue(int initialCapacity, Comparator<? super E> comparator) | 可以大堆创建初始容量为initialCapacity的优先级队列 |
class IntCmp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o1.compareTo(o2);//小根堆,如果o1和o2互换就是大根堆}
}
注意:
1、使用时必须导入PriorityQueue所在的包
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException异常
3. 不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为
6. PriorityQueue底层使用了堆数据结构
7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素
top-k:最小的k个数
public static int[] smallestK(int[] arr,int k){PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();for (int i = 0; i < arr.length; i++) {priorityQueue.offer(arr[i]);//时间复杂度NlogN}int[] tmp = new int[k];for (int i = 0;i<k;i++){int val = priorityQueue.poll();//时间复杂度k*logNtmp[i] = val;}//整个时间复杂度是(N+k)logN,不是真的top-k的解法return tmp;}
最小的k个数->建立大小为k的大根堆
最大的k个数->建立大小为k的小根堆
class IntCmp implements Comparator<Integer>{@Overridepublic int compare(Integer o1, Integer o2) {return o2.compareTo(o1);//大根堆,如果o1和o2互换就是小根堆}
}
public static int[] smallestK2(int[] arr,int k){int[] tmp = new int[k];if(k==0){return tmp;}PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());//大小为k的大根堆for (int i = 0; i < k; i++) {maxHeap.offer(arr[i]);//K*logK}//遍历剩下的元素for (int i = k;i<arr.length;i++){int val = maxHeap.peek();if(val>arr[i]){maxHeap.poll();maxHeap.offer(arr[i]);}}//(N-K)*logKtmp = new int[k];for (int i = 0;i<k;i++){tmp[i] = maxHeap.poll();//时间复杂度k*logN}return tmp;
//时间复杂度N*logK}