【Java 数据结构】 优先级队列(堆)

学习目标:

目标:熟练掌握Java所学知识


学习内容:

本文内容:优先级队列(堆)


一、优先级队列

1.1 概念

队列是一种先进先出的数据结构(FIFO)。但是有些情况下,操作的数据可能带有优先级,出队列时,可能需要优先级高的先出队列,在该场景下,显然不能使用普通队列;

比如: 用手机看视频时,这时有电话打进来,系统就会优先处理打进来的电话

在这种情况下,我们的数据结构应该提供两个基本操作,一个是返回优先级最高的对象,一个是添加新的对象。这种数据结构成为优先级队列

1.2常用接口介绍

1.2.1 PriorityQueue特性

Java集合提供了PriorityQueuePriorityBlockingQueqe两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的

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

import java.util.PriorityQueue

  1. PriorityQueue中放的元素必须能够比较大小,不能插入无法比较大小的元素,否则会抛出ClassCastException异常;
  2. 不能插入null对象,否则会抛出NullPointerException异常;
  3. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容;
  4. 插入和删除元素的时间复杂度都是O㏒(2N);
  5. PriorityQueue底层使用的堆数据结构 后文会介绍到)

1.2.2 PriorityQueue常用接口介绍

  1. 优先级队列的构造
构造器 功能介绍
PriorityQueue 创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity) 创建一个初始容量为initialCapacity大小的优先级队列
PriorityQueue(Collection<?extends E>c) 用一个集合来初始化优先级队列
public class TestPriorityQueue {
    
    
   public static void main(String[] args) {
    
    
       //创建一个空的优先级队列,默认容量为11
       PriorityQueue<Integer> q1=new PriorityQueue<>();

       //创建一个空的优先级队列,底层的容量为100
       PriorityQueue<Integer> q2=new PriorityQueue<>();

       ArrayList<Integer> list =new ArrayList<>();
       list.add(1);
       list.add(2);
       list.add(3);

       //用一个ArrayList对象构造优先级队列
       //此时优先级队列已经有3个元素
       PriorityQueue<Integer> q3=new PriorityQueue<>(list);
   }
}
  1. 优先级队列的常用方法
函数名 功能
boolean offer(E e) 插入元素e,插入成功返回true。如果e为空,则抛出NullPointerException异常,时间复杂度为O㏒(2N),空间不够时会自动扩容
E peek() 获取优先级最高的元素,入队优先级队列为空,则返回null
E poll() 移除优先级最高的元素并返回,如果优先级队列为空则返回null
int size() 获取有效元素个数
void clear() 清空优先级队列
boolean isEmpty() 检测优先级队列是否为空,为空则返回true
public static void testPriorityQueue2(){
    
    
        int[] arr={
    
    9,5,2,7,4,8,3,6};

        //如果知道元素个数,可以在创建优先级队列时,直接将容量设置好
        //否则在插入时会有不必要的扩容操作,效率较低
        PriorityQueue<Integer> q=new PriorityQueue<>(arr.length);
        //将数组元素插入到优先级队列中
        for(int i:arr){
    
    
            q.offer(i);
        }

        //获取优先级最高的元素
        System.out.println(q.peek());//运行结果  2
        //获取优先级队列有效元素个数
        System.out.println(q.size());//运行结果  8

        q.poll();//删除优先级队列中优先级最高的元素

        System.out.println(q.peek());//运行结果  3
        System.out.println(q.size());//运行结果  7

        q.offer(0);//插入元素0
        System.out.println(q.peek());//运行结果  0

    }

1.3 优先级队列的应用

top-k问题:最小的k个数

class Solution {
    
    
    public int[] getLeastNumbers(int[] arr, int k) {
    
    
        if(arr==null||k<=0){
    
    
            return new int[0];
        }
        PriorityQueue<Integer> queue=new PriorityQueue<>(arr.length);
        //将数组元素插入优先级队列中
        for(int i=0;i<arr.length;i++){
    
    
            queue.offer(arr[i]);
        }
        int[] res=new int[k];
        //得到优先级队列的前k个数
        for(int i=0;i<k;i++){
    
    
            res[i]=queue.poll();
        }
        return res;
    }
}

二、优先级队列的模拟实现

2.1堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

2.2堆的存储方式

在这里插入图片描述

2.3 堆的创建

2.3.1 堆向下调整

这个代码是以小堆为例

   public static void shiftDown(int[] arr, int index, int size) {
    
    
        int parent = index;//需要调整的位置
        int child = 2 * parent + 1;//chile标记parent的左孩子节点
        while (child < size) {
    
    
            if (child + 1 < size && arr[child] > arr[child + 1]) {
    
    
                //判断parent左右孩子的大小关系,让child标记较大的孩子节点
                child = child + 1;
            }
            //判断parent和child位置的元素的大小关系,
            //如果arr[child] < arr[parent],则交换位置
            if (arr[child] < arr[parent]) {
    
    
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
            } else {
    
    
                //否则arr[parent]就已经找到了合适的位置结束循环
                break;
            }
            parent = child;//更新parent标记的位置
            child = 2 * parent + 1;//更新child标记的位置,继续下次循环
        }
    }

2.3.2 堆的向上调整

同样是以小堆为例

    public  void shiftUp(int[] arr, int size, int index) {
    
    
        int child = index;//让child标记要调整元素的位置
        int parent = (child - 1) / 2;//child位置的父亲位置
        while (child > 0) {
    
    
            //如果arr[child] < arr[parent],则交换位置
            if (arr[child] < arr[parent]) {
    
    
                int temp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = temp;
            } else {
    
    
                //否则就找到的index位置元素该在的位置
                break;
            }
            child = parent;//更新child位置,进行下次循环
            parent = (child - 1) / 2;//更新parent位置,进行下次循环
        }
    }

2.3.3删除堆顶元素

删除堆顶元素可直接将堆顶元素与最后一个元素交换,将堆的有效元素个数减一,然后对堆顶元素进行向下调整即可

    public  Integer poll() {
    
    
        if (size == 0) {
    
    
            return null;
        }
        int res = arr[0];
        //交换操作
        int temp = arr[0];
        arr[0] = arr[size - 1];
        arr[size - 1] = temp;
        size--;//堆的有效元素个数减一就实现了删除元素
        shiftDown(arr, 0, size);//对堆顶元素进行向下调整
        return res;
    }

2.3.4取堆顶元素

   public  Integer peek() {
    
    
        if (size == 0) {
    
    
            return null;
        }
        int res = arr[0];
        return res;
    }

猜你喜欢

转载自blog.csdn.net/zhangxxin/article/details/115379410