第五节:Java集合框架之优先级队列PriorityQueue(堆)

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第五节:Java集合框架之优先级队列PriorityQueue(堆)相关的知识,希望对你有一定的参考价值。

文章目录

  • Java集合框架中PriorityQueue底层使用堆实现,所以本文先介绍堆的概念和实现

一:堆基本概念

(1)什么是堆

:堆就是一颗完全二叉树,这颗完全二叉树有这样一个特点:它的结点要么大于任意一个孩子结点,要么小于任意一个孩子结点

  • 如果其结点大于任意一个孩子结点,就称其为大顶堆,此时其最大的结点是根结点
  • 如果其结点小于任意一个孩子结点,就称其为小顶堆,此时其最小的结点是根结点

(2)堆存储方式

堆存储方式:堆是一颗完全二叉树,所以可以利用一个数组来存储,数组元素顺序正好对应二叉树的层次遍历序列

一旦将堆中的元素存储到数组后,根据二叉树性质就会有以下结论,假设 i i i为某结点在数组中的下标

  • 如果 i i i为0,则代表根结点,否则 i i i结点的双亲结点 i − 1 2 \\fraci-12 2i1
  • 如果 2 i + 1 2i+1 2i+1<结点个数,则结点 i i i的左孩子下标为 2 i + 1 2i+1 2i+1,否则没有左孩子
  • 如果 2 i + 1 2i+1 2i+1>结点个数,则结点 i i i的右孩子下标为 2 i + 1 2i+1 2i+1,否则没有右孩子

二:堆的模拟实现

  • 以创建小顶堆为例

(1)重点操作说明

A:堆的初始化

堆的初始化:程序开始时会给出一个数组,这个数组存储了某个完全二叉树的层次遍历结果,但此时它并不是堆,如下

  • 给定数组【27, 15, 19, 18, 28, 34, 65, 49, 25, 37】

B:堆的向下调整

堆的向下调整:上面的完全二叉树并不是堆,所以需要进行向下调整,把较小的结点换到上面去

下图展示了一次调整过程

下图是动态调整过程

C:堆的构造

上面堆的向下调整算法要求左右子树必须是堆,但一般不会有这样的理想情况。那么为什么还要讲堆的向下调整算法呢?其实对于一个完全二叉树,从根节点角度看,确实满足不了情况。但是,去考察极限情况——一个结点就是一个完全二叉树,它也一定是一个堆,所以可以从最后一个非叶结点开始(也可以从最后一个结点开始,只不过,如果从最后一个结点开始效率不高),对每一个结点使用堆的向下调整,直到根节点,这样就能构造一个小顶堆了。如果一个完全二叉树有n个结点,那么它的最后一个非叶结点的编号为 n 2 − 1 \\fracn2-1 2n1

D:堆的插入

插入元素时,新元素的到来很可能会破坏当前堆的结构,所以可以重新建立堆。而与向下调整算法相反,堆插入时使用的是向上调整算法。如下

E:堆的删除

堆删除元素默认删除第一个,因为删除其它元素没有意义。删除时,将最后一个元素与第一个元素交换,然后对新的根进行向下调整即可

(2)代码

package myHeap;

import java.util.Arrays;

public class MyHeap 
    public int[] elem;
    public int usedSize;//当前堆中有效数据个数

    public MyHeap()
        this.elem = new int[10];
        this.usedSize = 0;
    

    //传入数组
    public void initArray(int[] array)
        this.elem = Arrays.copyOf(array, array.length);
        this.usedSize = this.elem.length;
    

    //交换
    private void swap(int[]array, int i, int j)
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    
    //向下调整算法
    private void adjustDown(int parent, int len)
        int child = 2 * parent + 1;
        while(child < len)
            //建立小顶堆,那么就要找到更小的
            //注意child+1可能越界
            if(child+1 < usedSize && this.elem[child+1] < this.elem[child])
                child++;
            
            //如果孩子更小,那么交换
            if(this.elem[child] < this.elem[parent])
                //交互完成后,继续向下走
                swap(this.elem, child, parent);
                parent = child;
                child = 2 * parent + 1;
            else
                break;
            

        
    
    //向上调整算法
    private void adjustUp(int child)
        int parent = (child-1) / 2;
        while(child > 0)
            if(this.elem[child] < this.elem[parent])
                swap(elem, child, parent);
                child = parent;
                parent = (child-1) / 2;
            else
                break;
            
        
    

    //显示
    public void display()
        System.out.println(Arrays.toString(this.elem));
    

    //堆的建立(以小顶堆为例)
    public void createHeap()
     //从最后一个非叶结点开始依次向下调整
     for(int parent = (usedSize-1-1)/2; parent>=0; parent--)
         adjustDown(parent, usedSize);
     
    

    //堆的插入
    public void insert(int val)
        if(isFull())
            this.elem = Arrays.copyOf(this.elem, this.elem.length*2);
        
        this.elem[usedSize] = val;
        usedSize++;
        adjustUp(usedSize-1);
    

    //堆的删除
    public int remove()
        if(isEmpty())
            return -1;
        
        int retVal = this.elem[0];
        swap(this.elem, 0, usedSize-1);
        usedSize--;
        adjustDown(0 ,usedSize);
        return retVal;
    

    //判满
    public boolean isFull()
        return this.usedSize == this.elem.length;
    

    //判空
    public boolean isEmpty()
        return this.usedSize == 0;
    


三:PriorityQueue使用

(1)基本使用

PriorityQueue:PriorityQueue是指优先级队列,底层使用堆实现

  • Java集合框架中有PriorityQueue(线程不安全)和PriorityBlockingQueue(线程安全)这两种优先级队列

使用时注意

  • PriorityQueue中放置的元素必须能够比较大小,否则会抛出ClassCastException异常
  • 不要插入null对象,否则抛出NullPointerException
  • PriorityQueue默认建立小顶堆,如果想要创建大顶堆则用户需要提供比较器

涉及方法如下

  • boolean offer(E, e):插入元素e
  • E peek():获取优先级最高的元素
  • E poll():移除优先级最高的元素并返回
  • int size():获取有效元素个数
  • void clear():清空
  • boolean isEmpty():判空

下面是一个例子

import java.util.PriorityQueue;

public class TestDemo 
    public static void main(String[] args) 
        int[] arr = 4, 1, 9, 2, 8, 9, 9, 7, 3, 6 ,5;
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        for(int e : arr)
            priorityQueue.offer(e);
        
        System.out.println("打印优先级队列" + priorityQueue);
        System.out.println("打印优先级队列中有效元素个数" + priorityQueue.size());
        System.out.println("获取优先级最高的元素" + priorityQueue.peek());
        priorityQueue.poll();
        System.out.println("删除两个优先级最高的元素然后再次获取" + priorityQueue.peek());
        System.out.println("判断优先级队列是否为空" + priorityQueue.isEmpty());
        priorityQueue.clear();
        System.out.println("清空优先级队列并判空" + priorityQueue.isEmpty());
    

(2)说明

前面说过, PriorityQueue中放置的元素必须能够比较大小,否则会抛出ClassCastException异常。因此在存放一些自定义数据类型时,该数据类型需要实现Comparable接口,如下

import java.util.PriorityQueue;


class People implements Comparable<People>
    public int age;

    public People(int age)
        this.age = age;
    
    @Override
    public int compareTo(People p)
        return this.age - p.age;
    

public class TestDemo 
    public static void main(String[] args) 
        PriorityQueue<People> priorityQueue = new PriorityQueue<>();
        priorityQueue.offer(new People(13));
        priorityQueue.offer(new People(27));
        priorityQueue.offer(new People(99));
        priorityQueue.offer(new People(19));
        priorityQueue.offer(new People(7));
        System.out.println("最小年龄是:" + priorityQueue.peek().age + "岁");
    

以上是关于第五节:Java集合框架之优先级队列PriorityQueue(堆)的主要内容,如果未能解决你的问题,请参考以下文章

栈和队列-第五节:JavaC++Python实现栈和队列

第五节:Java中常用数据集合

第五节:mybatis之动态SQL

第五节 索引

Java 集合框架PriorityQueue 优先级队列的使用

Java 集合框架PriorityQueue 优先级队列的使用