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

news2024/12/30 2:35:05

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

文章目录

  • 数据结构 优先级队列(堆)
    • 1. 优先级队列
      • 1.1 概念
    • 2. 优先级队列的模拟实现
      • 2.1 堆的概念
      • 2.2 堆的存储方式
      • 2.3 堆的创建
        • 2.3.1 堆向下调整
        • 2.3.2 堆的创建
        • 2.3.3 建堆的时间复杂度
      • 2.4 堆的插入与删除
        • 2.4.1 堆的插入
        • 2.4.2 堆的删除
      • 2.5 用堆模拟实现优先级队列
    • 3. 常用接口介绍
      • 3.1 PriorityQueue的特性
      • 3.2 PriorityQueue常用接口介绍
        • **3.2.1 优先级队列的构造**
        • 3.2.2 插入/删除/获取优先级最高的元素
      • 3.3 OJ练习
    • 4. 堆的应用

1. 优先级队列

1.1 概念

在前面的文章里曾介绍过队列,队列是一种先进先出的数据结构,但有些情况下,我们需要操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列

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

2. 优先级队列的模拟实现

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

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 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以采用顺序存储的方式来进行高效存储

在这里插入图片描述

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

将元素存储到数组中后,可以根据我们在二叉树文章中介绍到的性质5堆树进行还原。假设i为节点在数组中的下标,则有:

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

2.3 堆的创建

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 = child;child = parent * 2 + 1

在这里插入图片描述

代码示例:

public void shiftSmallDown(int parent,int len) {
        //左孩子
        int child = 2 * parent + 1;
        while(child < len) {
            //判断是否存在右孩子 且右孩子和左孩子哪个更小,小的为child
            if (child+1 < len &&  elem[child+1] < elem[child]) {
                child++;
            }
            //判断根节点和child的大小,若根节点小则结束遍历,否则交换两个节点
            if (elem[child] < elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            }
            else {
                break;
            }
        }
    }

public void createSmallHeap() {
        for (int parent = (usderSize-1-1)/2;parent >= 0;parent--) {
            shiftSmallDown(parent, usderSize);
        }
    }

public void swap(int x, int y) {
        int temp = elem[x];
        elem[x] = elem[y];
        elem[y] = temp;
    }

在这里插入图片描述

2.3.2 堆的创建

上述举的例子为特殊情况(即左右子树已经满足堆的特性,只需要修改根节点),那对于普通的序列{1, 5, 3, 8, 7, 6},即根节点的左右子树不满足堆的特性时,又该如何调整?(这里我们以大根堆为例)

在这里插入图片描述

在这里,我们找倒数第一个非叶子节点从该节点位置开始往前一直到根节点,每遇到一个节点,应用向下调整

代码示例:

public void shiftBigDown(int parent,int len) {
        //左孩子
        int child = 2 * parent + 1;
        while(child < len) {
            //判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为child
            if (child+1 < len &&  elem[child+1] > elem[child]) {
                child++;
            }
            //判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            }
            else {
                break;
            }
        }
    }
public void swap(int x, int y) {
        int temp = elem[x];
        elem[x] = elem[y];
        elem[y] = temp;
    }
public void createBigHeap() {
    //找倒数第一个非叶子节点,从该节点位置开始往前一直到根节点,每遇到一个节点,应用向下调整
        for (int parent = (usderSize-1-1)/2;parent >= 0;parent--) {
            shiftBigDown(parent, usderSize);
        }
    }

在这里插入图片描述

2.3.3 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):

在这里插入图片描述

因此,建堆的时间复杂度为O(N)

2.4 堆的插入与删除

2.4.1 堆的插入

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

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

在这里插入图片描述

代码示例:

public void push(int val) {
        // 满了则扩容
        if (isFull()) {
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usderSize] = val;

        //向上调整
        shiftBigUp(usderSize);
        usderSize++;
    }

    private boolean isFull() {
        return usderSize == elem.length;
    }

    /**
     * 向上调整
     * 这里以大根堆为例
     */
    public void shiftBigUp(int child) {
        int parent = (child - 1)/2;
        while(child > 0) {
            if (elem[parent] < elem[child]) {
                swap(child,parent);
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }
    public void swap(int x, int y) {
        int temp = elem[x];
        elem[x] = elem[y];
        elem[y] = temp;
    }

在这里插入图片描述

2.4.2 堆的删除

堆删除的一定是堆顶元素,操作方法如下:

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

在这里插入图片描述

代码示例:

 /*删除元素*/
public int pop() {
        if (empty()) {
            return -1;
        }
        int oldVal = elem[0];
        swap(0,usderSize-1);
        usderSize--;
        shiftBigDown(0,usderSize);
        return oldVal;
    }
public boolean empty() {
        return usderSize == 0;
    }

public void shiftBigDown(int parent,int len) {
        //左孩子
        int child = 2 * parent + 1;
        while(child < len) {
            //判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为child
            if (child+1 < len &&  elem[child+1] > elem[child]) {
                child++;
            }
            //判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            }
            else {
                break;
            }
        }
    }

在这里插入图片描述

2.5 用堆模拟实现优先级队列

代码示例:

package demo2;

public class MyPriorityQueue {
    public int[] arr = new int[100];
    public int size = 0;

    public void offer(int e) {
        arr[size] = e;
        shiftBigUp(size);
        size++;
    }

    public int poll() {
        int oldval = arr[0];
        swap(0,size-1);
        size--;
        shiftBigDown(0,size);
        return oldval;
    }

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

    public void shiftBigUp(int child) {
        int parent = (child - 1)/2;
        while(child > 0) {
            if (arr[parent] < arr[child]) {
                swap(child,parent);
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }

    public void shiftBigDown(int parent,int len) {
        //左孩子
        int child = 2 * parent + 1;
        while(child < len) {
            //判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为child
            if (child+1 < len &&  arr[child+1] > arr[child]) {
                child++;
            }
            //判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点
            if (arr[child] > arr[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            }
            else {
                break;
            }
        }
    }

    public void swap(int x, int y) {
        int temp = arr[x];
        arr[x] = arr[y];
        arr[y] = temp;
    }

    public void display() {
        for (int i = 0;i < size;i++) {
            System.out.print(arr[i] + " ");
        }
    }
}

在这里插入图片描述

3. 常用接口介绍

3.1 PriorityQueue的特性

Java集合框架中提供了PriorityQueuePriorityBlockingQueue两种类型的优先级队列,本文主要介绍PriorityQueue

在这里插入图片描述

使用PriorityQueue时需要注意:

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

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

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

  4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

  5. 插入和删除元素的时间复杂度为O(log₂N)

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

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

3.2 PriorityQueue常用接口介绍

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

import java.util.ArrayList;
import java.util.PriorityQueue;

public class TestPriorityQueue {

    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(1234);
        list.add(123);
        list.add(12);
        list.add(1);

        // 用ArrayList对象来构造一个优先级队列的对象
        //此时q3中已经包含了三个元素
        PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
        System.out.println(q3);
        System.out.println("size = " + q3.size());
        System.out.println("队头元素为:" + q3.peek());

    }

}

在这里插入图片描述

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

class IntCmp implements Comparator<Integer> {
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}

//大根堆构建优先级队列
        PriorityQueue<Integer> q4 = new PriorityQueue<>(new IntCmp());
        q4.offer(4);
        q4.offer(3);
        q4.offer(2);
        q4.offer(1);
        q4.offer(5);
        System.out.println(q4);

在这里插入图片描述

此时创建出来的就是一个大根堆

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

import java.util.PriorityQueue;

public class TestPriorityQueue2 {
    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);
        System.out.println(q.peek()); // 获取优先级最高的元素
        System.out.println(q.size()); // 打印优先级队列中有效元素的个数

        //弹出元素
        q.poll();
        System.out.println(q);

        q.offer(0); // 插入元素后优先级会发生改变
        System.out.println(q);

        //清空队列
        q.clear();
        if (q.isEmpty()) {
            System.out.println("优先级队列为空");
        }
        else {
            System.out.println("优先级队列不为空");
        }
    }
}

在这里插入图片描述

3.3 OJ练习

top-k问题求最大或者最小的前k个数据

代码示例:

import java.util.PriorityQueue;
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if (k == 0 || arr == null) {
            return ret;
        }
        PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
        for (int e:arr) {
            q.offer(e);
        }
        for (int i = 0;i < k;i++) {
            ret[i] = q.poll();
        }
        return ret;
    }
}

在这里插入图片描述

4. 堆的应用

这里我们拓展一下堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆:

    • 升序:建大堆
    • 降序:建小堆
  2. 利用堆删除思想来进行排序:

    建堆和堆删除中都用到了向下调整,因此掌握向下调整,就可以完成堆排序

在这里插入图片描述

代码示例:

public void heapSort() {
        int end = usderSize - 1;
        while(end > 0) {
            swap(0,end);
            shiftBigDown(0,end-1);
            end--;
        }
    }

public void shiftBigDown(int parent,int len) {
        //左孩子
        int child = 2 * parent + 1;
        while(child < len) {
            //判断是否存在右孩子 且右孩子和左孩子哪个更大,大的为child
            if (child+1 < len &&  elem[child+1] > elem[child]) {
                child++;
            }
            //判断根节点和child的大小,若根节点大则结束遍历,否则交换两个节点
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = 2 * parent + 1;
            }
            else {
                break;
            }
        }
    }

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1093410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于微服务+Java+Spring Cloud开发的建筑工地智慧平台源码 云平台多端项目源码

建筑工地智慧平台源码&#xff0c;施工管理端、项目监管端、数据大屏端、移动APP端全套源码 技术架构&#xff1a;微服务JavaSpring Cloud VueUniApp MySql自主版权实际应用案例演示 建筑工地智慧平台已应用于线上巡查、质量管理、实名制管理、危大工程管理、运渣车管理、绿色…

非连续分配管理方式之基本分页存储管理

连续分配&#xff1a;为用户进程分配的必须是一个连续的内存空间。 非连续分配&#xff1a;为用户进程分配的可以是一些分散的内存空间。 基本分页存储管理的思想∶把进程分页&#xff0c;各个页面可离散地放到各个的内存块中。 1.分页存储 1.内存空间分区 将内存空间分为一…

数据库 MySql快速导入外部数据库流程

适用于新安装MySql本地没有数据情况 外部MySql数据库文件 任务管理器停用Mysql进程 将外部文件替换本地默认文件即可 重启电脑导入完成。

MyLife - Docker安装rabbitmq

Docker安装rabbitmq 个人觉得像rabbitmq之类的基础设施在线上环境直接物理机安装使用可能会好些。但是在开发测试环境用docker容器还是比较方便的。这里学习下docker安装rabbitmq使用。 1. rabbitmq 镜像库地址 rabbitmq 镜像库地址&#xff1a;https://hub.docker.com/_/rabbi…

并联谐振DCDC变换器的设计与仿真

摘 要 在我们日常生活中&#xff0c;并联谐振变换器随处可见&#xff0c;因为其相比其他变换器而言结构相对简单&#xff0c;运行稳定且便于维修等优势&#xff0c;最重要的是并联谐振变换器在并网方面具有很好的优势。随着自动控制技术和微电子技术的不断革新&#xff0c;目前…

并联机器人结构分析与领域应用

并联机器人早在20世纪的90年代就已经崭露头角&#xff0c;具有刚度高、速度快、柔性强、重量轻等优点&#xff0c;是工业机器人的新生代力量。并联机器人与串联机器人一起构成了工业机器人的重要部分。在食品、医药、电子等轻工业中应用最为广泛&#xff0c;在物料的搬运、包装…

记录Bug:VScode中无法识别万能头文件#include<bits/stdc++.h>

问题&#xff1a; 在VScode中使用万能头文件#include<bits/stdc.h>编写程序时报错&#xff1a;“检测到 #include 错误。请更新 includePath。已为此翻译单元(D:\Code_C\desC。。。。”。但是普通的c语言头文件#include <stdio.h>等可以正常运行。 原因&#xff1…

Linux网络编程系列之UDP组播

一、什么是UDP组播 UDP组播是指使用用户数据报协议&#xff08;UDP&#xff09;实现的组播方式。组播是一种数据传输方式&#xff0c;允许单一数据包同时传输到多个接收者。在UDP组播中&#xff0c;一个数据包可以被多个接收者同时接收&#xff0c;这样可以降低网络传输的负载和…

P1433 吃奶酪

#include <iostream> #include <cmath> using namespace std; #define M 15 #define S(n) ((n) * (n)) double indx[M 5], indy[M 5], ans 0, sum 0;//坐标数组&#xff0c;从下标为1开始记录 int n, vis[M 5] { 0 };//vis数组&#xff0c;选过的数字标记为1…

N点复序列求2个N点实序列的快速傅里叶变换

一、方法简介 通过一个点复数序列求出两个点实数序列的离散傅里叶变换&#xff0c;进一步提升快速傅里叶变换的效率。 二、方法详解 和是实数序列&#xff0c;且长度都为&#xff0c;定义复数序列&#xff1a; &#xff0c; 则序列和可表示为&#xff1a; 的离散傅…

openssl学习——消息认证码原理

消息认证码原理 消息认证码&#xff08;Message Authentication Code, MAC&#xff09;是一种技术&#xff0c;它的原理是通过对消息和密钥进行特定的处理&#xff0c;生成一个固定长度的数据&#xff0c;这个数据就是消息认证码&#xff08;MAC&#xff09;。这个过程可以看作…

【10】基础知识:React - DOM的diffing算法

一、虚拟 DOM 中 key 的作用 react/vue 中的 key 有什么作用&#xff1f;key的内部原理是什么&#xff1f; 简单来说&#xff1a; key 是虚拟 DOM 对象的标识&#xff0c;在更新显示时 key 起着极其重要的作用&#xff0c;提高渲染效率&#xff0c;防止渲染错误。 详细的说…

和琪宝的深圳,香港之旅~

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 引言 国庆期间原定和琪宝的旅游计划是深圳-香港-厦门。但是奈何在去厦门的前一天&#xff0c;…

STM32 BootLoader设置

编写bootloader程序&#xff1a; 直接复制下面代码到自己程序中。 typedef void (*iapfun)(void); //定义一个函数类型的参数. iapfun jump2app; //设置栈顶地址 //addr:栈顶地址 __asm void MSR_MSP(u32 addr) {MSR MSP, r0 //set Main Stack valueBX r14 }//跳转到…

TikTok国际版 使用特网科技Bluestacks模拟器安装方法

特网科技Bluestacks模拟器主机 桌面自带Bluestacks模拟器 TikTok国际版Bluestacks模拟器搜索tiktot 登录google应用商店-安装TikTok 安装过程可能需要3-5分钟不等-配置过低可能会导致安装失败&#xff0c;建议升级更高内存。 安装完成-打开 安装成功APP-我的游戏查看 打开国际版…

phpcms_v9模板制作及二次开发常用代码

0:调用最新文章&#xff0c;带所在版块 {pc:get sql"SELECT a.title, a.catid, b.catid, b.catname, a.url as turl ,b.url as curl,a.id FROM v9_news a, v9_category b WHERE a.catid b.catid ORDER BY a.id DESC " num"15" cache"300"} {lo…

光电柴微电网日前调度报告

摘要 微电网是目前国内外应用较为广泛的一种绿色可再生能源&#xff0c;近几年我国微电网产业的发展十分迅速。然后&#xff0c;越来越多的微电网系统建立并网&#xff0c;微电网产生的电能受外界因素影响较大&#xff0c;具有一定的随机性和波动性&#xff0c;给并网后的电力系…

leetCode 72. 编辑距离 动态规划

72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 编辑距离的应用场景&#xff1a;…

【内网攻击】DHCP协议概念——地址池耗尽攻击

目录 前言 DHCP 服务概念 1&#xff09;客户端发送DHCP Discovery广播包 2&#xff09;服务器响应DHCP Offer广播包 3&#xff09;客户机发送DHCP Request广播包 4&#xff09;服务器发送DHCP ACK广播包 部署DHCP服务器 dhcp地址池消耗攻击 攻击防御 前言 现在思考我们…

[论文分享] EnBinDiff: Identifying Data-Only Patches for Binaries

EnBinDiff: Identifying Data-Only Patches for Binaries [TDSC 2021] 在本文中&#xff0c;我们将重点介绍纯数据补丁&#xff0c;这是一种不会引起任何结构更改的特定类型的安全补丁。作为导致假阴性的最重要原因之一&#xff0c;纯数据补丁成为影响所有最先进的二进制差分方…