Java数据结构(十一):优先级队列(堆)

🌸雨落在了我的手上:个人主页

🐟个人仓库:Gitee仓库

❄️个人专栏:<<JaveSe>> <<C语言>> <<C语言数据结构>>**

🔥🔥🔥 人生格言:无人扶我青云志,我自踏雪至山巅

🎬 博主简介:


目录

一:优先级队列

1.概念

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

1.堆的概念

2.堆的存储⽅式

3.堆的创建

1.堆向下调整

2.堆的创建

4.堆的插⼊与删除

1.堆的插⼊

2.堆的删除

5.⽤堆模拟实现优先级队列

三:常⽤接⼝介绍

1.PriorityQueue的特性

2.PriorityQueue常⽤接⼝介绍

1.优先级队列的构造

2.插⼊/删除/获取优先级最⾼的元素


一:优先级队列

1.概念

前⾯介绍过队列,队列是⼀种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先 级,⼀般出队列时,可能需要优先级⾼的元素先出队列,该中场景下,使⽤队列显然不合适,⽐如: 在⼿机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位 时可能会让成绩好的同学先挑座位。

在这种情况下,数据结构应该提供两个最基本的操作,⼀个是返回最⾼优先级对象,⼀个是添加新的 对象。这种数据结构就是优先级队列(PriorityQueue)

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

JDK1.8中的PriorityQueue底层使⽤了堆这种数据结构,⽽堆实际就是在完全⼆叉树的基础上进⾏了 ⼀些调整。

1.堆的概念

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

堆的性质:

  • 堆中某个节点的值总是不⼤于或不⼩于其⽗节点的值;
  • 堆总是⼀棵完全⼆叉树。

2.堆的存储⽅式

从堆的概念可知,堆是⼀棵完全⼆叉树,因此可以层序的规则采⽤顺序的⽅式来⾼效存储

注意:对于⾮完全⼆叉树,则不适合使⽤顺序⽅式进⾏存储,因为为了能够还原⼆叉树,空间中必须 要存储空节点,就会导致空间利⽤率⽐较低。

将元素存储到数组中后,可以根据⼆叉树章节的性质5对树进⾏还原。假设i为节点在数组中的下标,则 有:

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

3.堆的创建

1.堆向下调整

对于集合{27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?

仔细观察上图后发现:根节点的左右⼦树已经完全满⾜堆的性质,因此只需将根节点向下调整好即 可。

向下过程(以⼩堆为例):

1.让parent标记需要调整的节点,child标记parent的左孩⼦(注意:parent如果有孩⼦⼀定先是有左 孩⼦)

2.如果parent的左孩⼦存在,即:child<size, 进行以下操作,直到parent的左孩子不存在

  • parent右孩⼦是否存在,存在找到左右孩⼦中最⼩的孩⼦,让child进⾏标
  • 将parent与较⼩的孩⼦child⽐较

如果:

  • parent⼩于较⼩的孩⼦child,调整结束
  • 否则:交换parent与较⼩的孩⼦child,交换完成之后,parent中⼤的元素向下移动,可能 导致⼦树不满⾜对的性质,因此需要继续向下调整,即parent=child;child= parent*2+1; 然后继续2。

核心代码:

java 复制代码
   /**
     * 向下调整(小顶堆):将指定节点与其较小的孩子交换,直到满足堆性质
     * @param array  待调整的数组
     * @param parent 当前需要调整的节点索引
     */
    public void shiftDown(int[] array, int parent) {
        int size = array.length;
        int child = 2 * parent + 1;   // 先标记左孩子

        while (child < size) {
            // 如果右孩子存在且比左孩子小,则 child 指向右孩子(较小者)
            if (child + 1 < size && array[child + 1] < array[child]) {
                child++;
            }

            // 如果父节点已经不大于孩子,说明以 parent 为根的子树已满足小顶堆
            if (array[parent] <= array[child]) {
                break;
            }

            // 交换父节点与较小的孩子
            int temp = array[parent];
            array[parent] = array[child];
            array[child] = temp;

            // 继续向下调整被交换的孩子节点
            parent = child;
            child = 2 * parent + 1;
        }
    }

注意:在调整以parent为根的⼆叉树时,必须要满⾜parent的左⼦树和右⼦树已经是堆了才可以向下调整。

2.堆的创建

那对于普通的序列{1,5,3,8,7,6},即根节点的左右⼦树不满⾜堆的特性,⼜该如何调整呢?

核心代码:

java 复制代码
    public static void createHeap(int[] array) {
        // 找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点
         //遇到一个节点,应用向下调整
        int root = ((array.length - 2) >> 1);
        for (; root >= 0; root--) {
            shiftDown(array, root);
        }
    }

4.堆的插⼊与删除

1.堆的插⼊

堆的插⼊总共需要两个步骤:

  1. 先将元素放⼊到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插⼊的节点向上调整,直到满⾜堆的性质

核心代码(向上调整):

java 复制代码
public void shiftUp(int[] array, int child) {
    int parent = (child - 1) / 2;
    while (child > 0) {
        // 如果父节点 >= 孩子,满足大顶堆,结束
        if (array[parent] >= array[child]) {
            break;
        }
        // 将双亲与孩⼦节点进⾏交换
        int temp = array[parent];
        array[parent] = array[child];
        array[child] = temp;

        // ⼩的元素向下移动,可能到值⼦树不满⾜对的性质,因此需要继续向上调增
        child = parent;
        parent = (child - 1) / 2;
    }
}

2.堆的删除

注意:堆的删除⼀定删除的是堆顶元素。具体如下:

  1. 将堆顶元素对堆中最后⼀个元素交换
  2. 将堆中有效数据个数减少⼀个
  3. 对堆顶元素进⾏向下调整

5.⽤堆模拟实现优先级队列

这里的向下和向上调整的代码在上面已经写过了,这里只写核心代码,辅助代码就不写了

核心代码:

java 复制代码
public class MyPriorityQueue {

    // ---------- 成员变量 ----------
    // 演示作用,固定容量为 100,不再考虑扩容
    private int[] array = new int[100];
    private int size = 0;   // 当前有效元素个数

    // ---------- 核心公共方法 ----------

    /**
     * 插入元素(入队)
     * @param e 要插入的元素值
     */
    public void offer(int e) {
        // 先检查队列是否已满(防止数组越界)
        if (size == array.length) {
            throw new RuntimeException("队列已满,无法插入");
        }

        // 1. 将新元素放在数组末尾,同时 size 加 1
        array[size++] = e;
        // 2. 对新元素进行向上调整,参数是刚刚插入的位置(size-1)
        shiftUp(size - 1);
    }

    /**
     * 弹出并返回堆顶元素(最大值)
     * @return 原堆顶的值
     */
    public int poll() {
        // 先检查队列是否为空
        if (size == 0) {
            throw new RuntimeException("队列为空,无法弹出");
        }

        // 1. 记录堆顶的值(用于返回)
        int oldValue = array[0];
        // 2. 用最后一个元素覆盖堆顶,同时 size 减 1(--size 先减后使用)
        array[0] = array[--size];
        // 3. 对新的堆顶进行向下调整(保持堆性质)
        shiftDown(0);
        // 4. 返回原堆顶值
        return oldValue;
    }

    /**
     * 查看堆顶元素(不弹出)
     * @return 当前最大值
     */
    public int peek() {
        // 检查队列是否为空
        if (size == 0) {
            throw new RuntimeException("队列为空,无法查看");
        }
        // 直接返回堆顶元素
        return array[0];
    }
}

三:常⽤接⼝介绍

1.PriorityQueue的特性

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列, PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本⽂主要介绍 PriorityQueue。

关于PriorityQueue的使⽤要注意:

使⽤时必须导⼊PriorityQueue所在的包,即:

java 复制代码
import java.util.PriorityQueue;
  1. PriorityQueue中放置的元素必须要能够⽐较⼤⼩,不能插⼊⽆法⽐较⼤⼩的对象,否则会抛出 ClassCastException异常
  2. 不能插⼊null对象,否则会抛出NullPointerException
  3. 没有容量限制,可以插⼊任意多个元素,其内部可以⾃动扩容
  4. PriorityQueue底层使⽤了堆数据结构
  5. PriorityQueue默认情况下是⼩堆---即每次获取到的元素都是最⼩的元素

2.PriorityQueue常⽤接⼝介绍

1.优先级队列的构造

此处只是列出了PriorityQueue中常⻅的⼏种构造⽅式,其他的学⽣们可以参考帮助⽂档

2.插⼊/删除/获取优先级最⾼的元素

以上就是全部内容了!!!!!

相关推荐
带多刺的玫瑰6 小时前
Leecode#26刷题之删除有序数组中的重复项
数据结构·算法·leetcode
洋不写bug8 小时前
二叉树(二) 常见基础操作解析|结点数、树高、查找结点、判断完全二叉树
java·开发语言·数据结构·完全二叉树·二叉树结点数·树高·查找结点
化雨成烟8 小时前
C++之AVL树
数据结构
wabs6669 小时前
关于二叉树【429.N叉树的层序遍历的思考】
数据结构·c++·算法·leetcode·二叉树
C++ 老炮儿的技术栈9 小时前
MFC CPtrArray的用法
开发语言·数据结构·c++·算法·mfc·c
不会就选b9 小时前
算法日常・每日刷题--<贪心>6
数据结构·算法·leetcode
青山木10 小时前
Hot 100 --- 跳跃游戏 II
java·数据结构·算法·leetcode·贪心算法
神明不懂浪漫11 小时前
【第四章】索引——B+树、回表,加快数据库的查找能力的利器
开发语言·数据结构·数据库·经验分享·笔记·b树
LuminousCPP12 小时前
数据结构 - 排序(二):快速排序从错误初版到优化版|双指针划分 + 三数取中 + 小区间插入优化
c语言·数据结构·笔记·算法·排序算法