赞
踩
目录
三、优先队列(PriorityQueue)和堆(Heap)的区别
一堆元素集合k={k0,k1...k(n-1)},把所有元素按照完全二叉树的顺序存储方式,存储在一个一维数组中,并且满足ki<=k(2i+1)且ki<=k(2i+2),则称为小堆(反之则为大堆)。
因此在堆的数据结构中,我们将根节点最大的堆称为大根堆或最大堆,将根节点最小的堆称为小根堆或最小堆 。
堆是一棵完全二叉树,因此可以像层序遍历一样采用顺序存储的方式来存储,更加高效。
注意:对于非完全二叉树则不适合使用顺序存储的方式。在还原二叉树时,空间中需要存储空节点,这会导致空间利用率降低
(1).小根堆中某个节点的值总是不小于其父节点的值,大根堆中某个节点的值总是不大于其父节点的值。
(2).堆总是一棵完全二叉树。
【1】.向下调整
1.让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)
2.如果parent的左孩子存在(child<size),则进行以下操作直到parent的左孩子不存在
(1).parent右孩子是否存在,存在找到左右孩子中的最小值,让child进行标记
(2).将parent与较小的孩子child进行比较
【1】.parent小于较小的孩子child时,结束调整
【2】.parent大于较小的孩子child时,交换parent与child的值。此时可能并不正确,因此需要继续向下调整,即parent=child;child=parent*2+1;然后重复上述过程
- public void shiftDown(int parent,int len){
- int child=2*parent+1;
- while(child<size){
- if(child+1<size&&elem[child+1]<elem[child]){
- child+=1;
- }
- if(elem[parent]<=elem[child]){
- break;
- }else{
- int tmp=elem[parent];
- elem[parent]=elem[child];
- elem[child]=tmp;
- parent=child;
- child=parent*2+1;
- }
- }
- }
注意:在整个向下调整的过程中时间复杂度为O (log2N).
【2】.向上调整
1.让child标记需要调整的节点,然后找到child的双亲节点parent。
2.如果双亲节点parent存在(chid>0),则进行以下操作直到双亲节点不存在
(1).将双亲节点与孩子节点进行交换
【1】.如果双亲节点比孩子节点小,parent满足堆的性质,调整结束
【2】.双亲节点大于孩子节点时,交换parent与child的值。此时可能并不正确,因此需要继续向上调整,即child=parent; parent=(child-1)/2; 然后重复上述过程。
- public void shiftUp(int child){
- int parent =(child-1)/2;
- while(child>0){
- if(elem[parent]>elem[child]){
- break;
- }else{
- int tmp=elem[parent];
- elem[parent]=elem[child];
- elem[child]=tmp;
- child=parent;
- parent=(child-1)/2;
- }
- }
- }
注意:在整个向上调整的过程中时间复杂度为O (N).
- public class Heap{
- public int[] elem;
- public int usedSize;
- public Heap(){
- this.elem=new int[10];
- }
- public void initElem(int[] arr){
- for(int i=0;i<arr.length;i++){
- elem[i]=arr[i];
- usedSize++;
- }
- }
- public void createHeap(){
- for(int parent=(usedSize-1-1)/2;parent>=0;parent--){
- shiftDown(parent,usedSize);
- }
- }
- }
注意:因为堆是完全二叉树,而满二叉树也是完全二叉树,因此简化为满二叉树来证明。
注意:堆的插入和删除时间复杂度均为O(log2N)
【1】.堆的插入
步骤:
1.将元素放到最后一个元素后边,空间不够时需要扩容。
2.将最后新插入的节点向上调整,直到满足要求。
- public void push(int val){
- if(isFull()){
- elem=Arrays.copyOf(elem,2*elem.length);
- }
- elem[usedSize++]=val;
- shiftUp(usedSize-1);
- }
- public boolean isFull(){
- return usedSize==elem.length;
- }
【2】.堆的删除
步骤:
1.将堆顶元素与堆中最后一个元素交换。
2.将堆中元素有效数据个数减少一个。
3.对堆顶元素进行向下调整。
- public void pop(){
- if(isEmpty()){
- return;
- }
- int tmp=elem[0];
- elem[0]=elem[usedSize-1];
- elem[usedSize-1]=tmp;
- usedSize--;
- shiftDown(0,usedSize);
- }
- public boolean isEmpty(){
- return usedSize==0;
- }
用堆作为底层结构封装优先级队列
堆排序是利用堆的思想进行排序,首先需要建一个堆,升序排序建大堆,降序排序建小堆;其次利用堆删除思想来进行排序
- public void HeapSort(){
- int end=usdSize-1;
- while(end>0){
- swap(elem,0,end);
- shiftDown(0,end);
- end--;
- }
- }
求数据集合中前K个最大的元素或最小的元素,一般情况下数据量比较大,普通排序就不可取,因此我们就需要用到堆来排序。
首先用数据集合中前K个元素建堆(求K个最大元素:建小堆;求K个最小元素:建大堆);然后用剩余的N-K个元素依次与堆顶元素比较,堆顶元素不满足则替换。
队列是一种先进先出的数据结构,但有时操作的数据会带有优先级,所以在出队列时可能需要优先级较高的的元素先出队列。因此数据结构需要提供:返回最高优先级对象和添加新对象这两个基本操作,这种数据结构就是优先级队列。
Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列。PriorityQueue是线程不安全的,而PriorityBlockingQueue是线程安全的,本文重点讲解PriorityQueue。
【1】.PriorityQueue():创建一个空的优先级队列,默认容量是11
PriorityQueue<Integer> priorityqueue = new PriorityQueue<>();
【2】.PriorityQueue(int initialCapacity):创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛出IllegalArgumentException异常
PriorityQueue<Integer> priorityqueue = new PriorityQueue<>(100);
【3】.PriorityQueue(Collection<?extends E> c) 用一个集合来创建优先级队列
- ArrayList<Integer> arraylist = new ArrayList<>();
- list.add(1);
- list.add(2);
- list.add(3);
- PriorityQueue<Integer> priorityqueue = new PriorityQueue<>(arraylist);
boolean offer(E e)
插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容。
E peek()
获取优先级最高的元素,如果优先级队列为空,返回null
E poll()
移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()
获取有效元素的个数
void clear()
清空
boolean isEmpty()
检测优先级队列是否为空,空返回true
如果容量小于64时,是按照oldCapacity的2倍方式扩容的。
如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的。
如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容。
【1】.PriorityQueue中放置的元素必须要能比较大小,不能插入无法比较大小的,否则会抛出ClassCastException异常
【2】.不能插入null对象,否则会抛出NullPointerException
【3】.没有容量限制,可以插入任意多个元素,其内部可以自动扩容
【4】.插入和删除元素的时间复杂度为O(log2N)
【5】.PriorityQueue底层使用了堆数据结构
【6】.PriorityQueue默认是小堆,如果需要大堆需要用户提供比较器
- // 自定义比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
- class IntCmp implements Comparator<Integer>{
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2-o1;
- }
- }
优先队列:是一种特殊的队列
堆:是一个很大的概念并不一定是完全二叉树,在前文中使用完全二叉树是因为这个很容易被数组储存,所以大部分我们用完全二叉树(数组)来储存堆,但是除了这种二叉堆之外我们还有二项堆、 斐波那契堆等这些堆就不属于二叉树。
优先对列和堆有什么区别呢?这里说的堆默认是我们最常使用的二叉堆,而二叉堆只是优先队列的一种是实现方式而已。而优先队列还有二项堆、平衡树、线段树等来实现。所以说优先队列和堆不是一个概念,如果非要说他们之间的关系就是:二叉堆只是优先队列的一种实现方式,而java的PriorityQueue也是基于这个实现的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。