优先级队列详解

简介: 优先级队列详解

优先级队列简介


PriorityQueue,即优先级队列。它可以保证每次出出来的数据是队列中最大或最小的元素。

JDK1.8中的PriorityQueue底层使用了堆这种数据结构。


关于堆


堆实际就是在完全二叉树的基础上进行了一些调整。


堆的性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。


为什么得用完全二叉树


我们来看下图就明白了:


a9eaa2b345eb41fb979e4d5a4faed645.png


首先我们得明白,堆中数据的存储是用数组来实现的,那么它们的存储结构就出来了:


e83ba3f10315450cac4c4ae328677bef.png


我们可以看到对于完全二叉树,它可以做到不浪费空间,而非完全二叉树可能会产生很多零碎的空间浪费了,因此我们采用完全二叉树.


将元素存储到数组中后,可以根据二叉树的性质对树进行还原。

假设 i 为节点在数组中的下标,则有:

如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2

如果2 * i + 1 小于总的节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子

如果2 * i + 2 小于总的节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子


用堆来实现优先级队列


先来看看优先级队列的常用方法


插入/删除/获取优先级最高的元素


1.boolean offer(E e)

插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容


2.E peek()

获取优先级最高的元素,如果优先级队列为空,返回null


3.E poll()

移除优先级最高的元素并返回,如果优先级队列为空,返回null


4.int size()

获取有效元素的个数


5.void clear()

清空


6.boolean isEmpty()

检测优先级队列是否为空,空返回true


模拟实现


public class PriorityQueue {
    public int[] elem;
    public int usedSize;  //记录元素个数
    public PriorityQueue() {
        elem = new int[11];  //数组初始大小为11
        usedSize = 0;
    }
    /**
     * 建堆的时间复杂度:O(n*logn)
     *
     * @param array
     */
    public void createHeap(int[] array) {   //将该数组变为优先级队列
        elem = array;
        usedSize = array.length;
        for(int i = (usedSize-2)/2; i >= 0; i--) {  //从最后一个元素的父节点开始依次向下调整
            shiftDown(i,usedSize);
        }
    }
    /**
     *
     * @param root 是每棵子树的根节点的下标
     * @param len  是每棵子树调整结束的结束条件
     * 向下调整的时间复杂度:O(logn)
     */
    private void shiftDown(int root,int len) {
        int a = root*2 + 1;   //a表示左边的子节点
        while(a < len) {    //向下调整直到a越界了
            if(a+1 < len && elem[a] < elem[a+1]) {  //如果root有右节点,并且右节点大于左节点,则令a等于右节点
                a = a+1;    //这里a就表示root的最大子节点
            }
            if(elem[root] < elem[a]) {  //如果最大子节点大于root节点,则两者交换
                int tmp = elem[a];
                elem[a] = elem[root];
                elem[root] = tmp;
                root = a;   //将root指向交换了的子节点
                a = root*2 + 1;  //a重新指向root的左子节的
            }else {
                break;   //不大于直接退出循环
            }
        }
    }
    /**
     * 入队:仍然要保持是大根堆
     * @param val
     */
    public void push(int val) {
        if(isFull()) {  //如果满了扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;  //将待添加的元素放在数组最后面
        shiftUp(usedSize-1);  //将添加的元素进行向上调整
    }
    private void shiftUp(int child) {
        int root = (child-1) / 2;  //找出child的父节点root
        while(root >= 0) {   
            if(elem[child] > elem[root]) {  //如果子节点大于父节点,则交换
                int tmp = elem[child];
                elem[child] = elem[root];
                elem[root] = tmp;
                child = root;   //将子节点指向父节点
                root = (child-1) / 2;  
            }else {
                break;
            }
        }
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }
    /**
     * 出队【删除】:每次删除的都是优先级高的元素
     * 仍然要保持是大根堆
     */
    public void pollHeap() {
        if(isEmpty()) {
            throw new RuntimeException("队列为空,无法删除元素");
        }
        elem[0] = elem[--usedSize];  //删除其实就是将最后一个元素覆盖第一个元素
        shiftDown(0,usedSize);  //然后对第一个元素进行向下调整
    }
    public boolean isEmpty() {
        return usedSize == 0;
    }
    /**
     * 获取堆顶元素
     * @return
     */
    public int peekHeap() {
        return elem[0];
    }
}


这里实现的是大根堆, 其实PriorityQueue底层是小根堆.


使用PriorityQueue的注意事项


1.使用时必须导入PriorityQueue所在的包,即:

import java.util.PriorityQueue;


2.PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出

ClassCastException异常.


3.不能插入null对象,否则会抛出NullPointerException

fac7380ef6674bbab0fcd09fd184562c.png


4.插入元素时, 若空间不足, 其内部可以自动扩容


b194c434b0644c95b549513076973877.png


可以看到, 如果原数组大小小于64, 则二倍扩容, 大于则1.5倍扩容.


0538ed2dd4c54469a0345bfaa32ae1fc.png


可以看到扩容最大为 Integer.MAX_VALUE 也就是2^31 - 1.


dd31ea86b468404abe122d739887d3c8.png


5.插入和删除元素的时间复杂度为: O(logN)


6.PriorityQueue底层使用了堆数据结构


7.PriorityQueue默认情况下是小堆—即每次获取到的元素都是最小的元素.


PriorityQueue常用接口


优先级队列的构造方法


可以看到有很多构造方法:


37be8ac2f71d4b849bb54a95527f7ea6.png


这里介绍几个我们常用的构造方法:


5e8a122ba1744d4294ff59e47b13da9f.png


默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器.

用到这个构造方法:


e8cfe4851d574cc1bb1c1de6ab363bc4.png

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
  @Override
  public int compare(Integer o1, Integer o2) {
  return o2-o1;  //这里如果是o1-o2则是小根堆
  }
}
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());
  }
}


相关文章
|
6月前
堆(优先级队列 PriorityQueue)
堆(优先级队列 PriorityQueue)
41 0
|
存储 安全 索引
认真研究队列中的优先级队列PriorityQueue
认真研究队列中的优先级队列PriorityQueue
75 0
|
Java 调度
【PriorityQueue优先级队列及其应用】
【PriorityQueue优先级队列及其应用】
|
6月前
|
存储 Java
优先级队列(堆)
优先级队列(堆)
45 3
|
3月前
|
存储 设计模式 算法
【C++】deque以及优先级队列
【C++】deque以及优先级队列
|
5月前
|
C++ 容器
【C++】学习笔记——优先级队列
【C++】学习笔记——优先级队列
41 0
|
6月前
|
存储 安全 Java
优先级队列
优先级队列
|
6月前
|
存储 安全
堆与优先级队列
堆与优先级队列
37 0
|
6月前
|
存储 算法 安全
堆 和 优先级队列(超详细讲解,就怕你学不会)
堆 和 优先级队列(超详细讲解,就怕你学不会)
|
6月前
|
存储 Java C++
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
Java集合篇之深度解析Queue,单端队列、双端队列、优先级队列、阻塞队列
55 0