当前位置: 首页 > article >正文

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

1. 概念

    优先级队列是一种抽象数据类型(ADT),它允许队列中维护的元素按优先级排序,优先级最高的元素会优先被处理。

2. 使用

2.1 优先级队列的构造

构造器
功能介绍
PriorityQueue()
创建一个空的优先级队列,默认容量是11
PriorityQueue(int  initialCapacity)
创建一个初始容量为 initialCapacity 的优先级队列,注意:
initialCapacity不能小于 1 ,否则会抛 IllegalArgumentException 异常
PriorityQueue(Collection<?
extends E> c)
用一个集合来创建优先级队列

代码示例:

      public static void main(String[] args) {
        // 创建一个空的优先级队列,底层默认容量是11
        PriorityQueue<Integer> q1 = new PriorityQueue<>();
        // 创建一个空的优先级队列,底层的容量为initialCapacity
        PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
        ArrayList<Integer> list = new ArrayList<>();
        list.add(4);
        list.add(3);
        list.add(2);
        list.add(1);
        // 用ArrayList对象来构造一个优先级队列的对象
        // q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3.size());
        System.out.println(q3.peek());
    }

运行结果如下:

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

2.2 方法

函数名
功能介绍
boolean offer(E e)
插入元素 e ,插入成功返回 true ,如果 e 对象为空,抛出 NullPointerException 异常,注意:空间不够时候会进行扩容
E peek()
获取优先级最高的元素,如果优先级队列为空,返回null
E poll()
移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()
获取有效元素的个数
void clear()
清空
boolean isEmpty()
检测优先级队列是否为空,空返回true

代码示例

public static void main(String[] args) {
        int[] arr = {4,1,9,2,8,0,7,3,6,5};
     // 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
     // 否则在插入时需要不多的扩容
     // 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
        PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
        for (int e: arr) {
            q.offer(e);
        }
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
     // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
        q.poll();
        q.poll();
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
        q.offer(0);
        System.out.println(q.peek()); // 获取优先级最高的元素
     // 将优先级队列中的有效元素删除掉,检测其是否为空
        q.clear();
        if(q.isEmpty()){
            System.out.println("优先级队列已经为空!!!");
        }
        else{
            System.out.println("优先级队列不为空");
        }
    }

运行结果如下:

3. 模拟实现

    JDK1.8 中的 PriorityQueue 底层使用了堆这种数据结构 ,而堆实际就是在完全二叉树的基础上进行了一些调整。

3.1 堆

1. 概念

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

2. 性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。
3. 存储方式
从堆的概念可知, 堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储。

注意

   1. 对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低

   2. 将元素存储到数组中后,假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

3.2 堆的创建(以小根堆为例)

数据以集合{ 27,15,19,18,28,34,65,49,25,37 }为例。

向下调整
1. parent 标记需要调整的节点, child 标记 parent 的左孩子 ( 注意: parent 如果有孩子一定先是有左孩子 )
2. 如果 parent 的左孩子存在,即 :child < size , 进行以下操作,直到 parent 的左孩子不存在:
       1. parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标记
       2. 将 parent 与较小的孩子 child 比较,如果parent小于较小的孩子 child ,调整结束;
          否则:交换parent 与较小的孩子 child ,交换完成之后, parent 中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child child = parent*2+1; 然后继续
代码示例:
    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() {
        for (int parent = (usedSize-1-1)/2; parent >= 0 ; parent--) {
            siftDown(parent,usedSize);
        }
    }
    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++;
            }
            //child下标 就是 左右孩子的最小值
            if(elem[child] < elem[parent]) {
                swap(child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

运行结果如下:

注意:
   1. 在调整以 parent 为根的二叉树时,必须要满足 parent 的左子树和右子树已经是堆了才可以向下调整。
   2. 创建小根堆与大根堆的区别在于向下调整(siftDown),变化树中元素的比较关系就可以转换大小根堆。

3.3 插入

堆的插入总共需要两个步骤:
1. 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
2. 将最后新插入的节点向上调整,直到满足堆的性质
代码示例
public void offer(int val) {
        if(isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        usedSize++;//11
        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;
            }
        }
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }

运行结果如下

3.4 删除

  • 将堆顶元素对堆中最后一个元素交换
  • 将堆中有效数据个数减少一个
  • 对堆顶元素进行向下调整

代码示例

    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;
    }

运行结果如下

注意

1. 堆的删除一定删除的是堆顶元素。

2. 对堆进行删除前要判断对是否为空。

3.5 其他方法

1. 获取队顶元素:

代码示例:

  public int peek() {
        return elem[0];
    }

运行结果如下

2. 获取队列容量

代码示例:

 public int size(){
        return usedSize;
    }

4. 注意事项

1. 使用时必须导入 PriorityQueue 所在的包,即:
import java . util . PriorityQueue ;
2. PriorityQueue 中放置的 元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException 异常
3. 不能 插入 null 对象,否则会抛出 NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. PriorityQueue 底层使用了堆数据结构
6.PriorityQueue 默认情况下是小堆 --- 即每次获取到的元素都是最小的元素
本文是作者学习后的总结,如果有什么不恰当的地方,欢迎大佬指正!!!


http://www.kler.cn/a/538692.html

相关文章:

  • 只需三步!5分钟本地部署deep seek——MAC环境
  • Centos挂载镜像制作本地yum源,并补装图形界面
  • Bash (Bourne-Again Shell)、Zsh (Z Shell)
  • 练习题(2025.2.9)
  • Android Studio超级详细讲解下载、安装配置教程(建议收藏)
  • 深度学习和机器学习的区别|自注意力机制和多头注意力机制的展示|售前面试题
  • 深度整理总结MySQL——事务隔离级别实现原理
  • 昇腾,mindie,镜像,部署vllm:第1篇,安装新的docker 镜像
  • 【MySQL】第一弹---MySQL 在 Centos 7环境安装
  • 前端高级面试题及其答案
  • git SourceTree 使用
  • 工作案例 - python绘制excell表中RSRP列的CDF图
  • 登录到docker里
  • 在 NXP Yocto 环境下实现 Qualcomm Wi-Fi Driver 的 GitLab CI/CD
  • 使用 SurrealDB 构建高效的 GraphQL 后端
  • DeepSeek深度思考:客户端(Android/iOS)架构设计指南
  • 使用opencv解析视频,通过图片比对,筛选出每一帧视频的变化
  • LibreOffice - pptx 转 pdf
  • Unity3D开发之2019.4.5f1版本IPointerClickHandler Bug
  • 后缀表达式(蓝桥杯19I)
  • Java面试题之:sql优化方式
  • 【C++高并发服务器WebServer】-12:TCP详解及实现
  • Office/WPS接入DeepSeek等多个AI工具,开启办公新模式!
  • 第一节 docker基础之---安装
  • torch_unbindtorch_chunk
  • 0207作业