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

news2024/10/7 12:25:54

前言

在上一章我们讲了二叉树,这一节我们来讲堆(优先级队列),所以想知道堆创建,可以看一下二叉树的一些简单概念。http://t.csdnimg.cn/4jUR6icon-default.png?t=N7T8http://t.csdnimg.cn/4jUR6

目录

前言

1.概念

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

2.1堆的概念

2.2堆的性质

2.3堆的存储方式

2.4堆的创建

2.4.1向下调整

1.向下调整思路

​编辑

 2.代码实现

 3.建堆的时间复杂度

2.5堆的插入

2.5.1向上调整

2.5.2堆插入代码实现:

2.6堆元素的删除

2.6.1思路 

2.6.2.代码实现

2.7获取堆的元素个数&&获取堆顶元素&&堆的打印

3.常用接口特性

3.1PriorityQueue的特性

注意:

 3.2PriorityQueue常用接口介绍


1.概念

我们知道队列是一种先进先出的数据结构,但是在某些情况下,操作的数据可能带有优先级,出队的时候,可能需要优先级较高的元素出队列。

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

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

2.1堆的概念

如果有一个集合K={0,k1,,k2,...,kn-1},把集合K的元素按照完全二叉树的顺序存储方式存储在一个一维数组中,并且满足:K(i)<=K(2I+1)且K(i)<=K(2i+2)  (K(i)>=K(2I+1)且K(i)>=K(2i+2) ),i=0,1,2,3... ,则叫做小堆(或大堆)。

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

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

2.2堆的性质

1.堆中的某个节点总是不大于或不小于其父节点的值。

2.堆总是一棵完全二叉树。

在jDK1.8中的优先级队列底层使用了堆这种数据结构,而堆其实就是就是完全二叉树的基础进行调整的。

2.3堆的存储方式

我们从堆的概念可以知道,堆是一棵完全二叉树,所以可以层序的规则采用顺序的方式存储

注意:对于非完全二叉树,不适合采用顺序方式进行存储。

原因:为了还原二叉树,空间中必须要存储空节点,就会导致空间利用率较低。

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

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

2.4堆的创建

2.4.1向下调整

我们拿集合{28,16,48,13,46,45,25,36,22,42}为例,如何将其创建成堆呢??

我们可以看到,此时根节点的左右子树都不满足堆的性质。所以我们需要对每个有子树的父节点进行向下调整。

1.向下调整思路

对于一棵完全二叉树,要其变成小根堆(或大根堆),我们需要满足根节点的左右子树都是小堆(大堆)。

规则1.找出父亲节点的左右节点中值较小(或较大)的节点。

           2.找出较小值(较大值)与父亲节点进行比较。

           3.小堆:若父亲节点比左右节点中的较小值大,则进行交换,再将较小值的位置给到父亲节点,再进行向下调整。当父亲节点的值小于左右节点中的较小值时,调整停止

              大堆:若父亲节点比左右节点中的较大值小,则进行交换,再将较大值的位置给到父亲节点,再进行向下调整。当父亲节点的值小于左右节点中的较小值时,调整停止

我们以创建小根堆为例:

对于上图中的二叉树,我们可以看到其左右子树并不是小堆。

所以我们需要先对其左右子树进行向下调整:

我们从根节点位置最大的一个开始,依次递归进行调整。

我们可以看到父亲节点(46)比孩子节点(42)要大,所以要进行交换。

再让父亲节点(P)走向孩子节点(C)的位置,但是由于此时父亲节点并没有孩子节点,停止调整。再让P从节点值为13的位置开始向下调整,此时由于左右节点值都大于13,满足堆的性质,不进行交换。 

依次类推:

当P走到节点值为48的位置时,再与左右孩子中的最小值进行比较,进行互换。

当P走到父亲节点(16)的位置时,进行向下调整,再让P往下走,但此时P所处的节点其满足堆的性质,不进行互换,调整停止。 

 

此时,根节点(28)的左右子树都已经满足堆的性质,现只需要对根节点进行向下调整,就可以得到一个小根堆。

 

至此,我们就得到一个小根堆。

我们如果要创建一个大根堆,思路也是与创建小根堆的思路一样,只是在交换值时,是交换孩子节点中的较大值。

 2.代码实现

对于上面我们所推的,

其小根堆为{13,16,25,22,42,45,48,36,28,46};

其大根堆为{48,46,45,36,42,28,25,13,22,26};

package MyQueue;

/**
 * Pheap类实现了大根堆数据结构。
 */
class Pheap {
    public int[] elem; // 存储堆元素的数组
    public int useSize; // 当前堆中元素的使用大小

    /**
     * 构造函数,初始化堆数组。
     *
     * @param size 堆数组的初始大小
     */
    public Pheap(int size){
        this.elem=new int[size];
    }

    /**
     * 使用给定数组初始化堆。
     *
     * @param arr 用于初始化堆的数组
     */
    public void init(int[] arr){
        for(int i=0;i<arr.length;i++){
            this.elem[i]=arr[i];
        }
        useSize=arr.length;
    }

    /**
     * 交换数组中两个元素的位置。
     *
     * @param child 需要交换的子元素下标
     * @param parent 需要交换的父元素下标
     */
    public void swap(int child,int parent){
        int temp=elem[child];
        elem[child]=elem[parent];
        elem[parent]=temp;
    }

    /**
     * 向下调整以维护大根堆性质。
     *
     * @param parent 需要向下调整的父节点下标
     * @param end 堆数组的结束下标
     */
    public void sitDownBig(int parent,int end){
        int child=2*parent+1;
        while(child<end){
            if(child+1<end&&elem[child]<elem[child+1]){
                child++;
            }
            if(elem[child]>elem[parent]){
                swap(child,parent);
                parent=child;
                child=2*parent+1;
            }else{
                break;
            }
        }
    }

    /**
     * 构建大根堆。
     */
    public void createHeapBig(){
        for(int parent=(useSize-1-1)/2;parent>=0;parent--){
             sitDownBig(parent,useSize);
        }
    }
    /**
     *构建小根堆
     */
    public void creatHeapSmall(){
        for(int parent=(useSize-1-1)/2;parent>=0;parent--){
            sitDownSmall(parent,useSize);
        }
    }

    /**
     * 将指定元素下沉以维护堆的性质。该方法用于调整二叉堆,确保从指定父节点到末尾子节点的子树满足堆的性质。
     *
     * @param parent 父节点的索引
     * @param end 堆数组的末尾索引
     */
    public void sitDownSmall(int parent, int end) {
        // 计算左子节点的索引
        int child = 2 * parent + 1;
        while (child < end) {
            // 如果存在右子节点,并且右子节点比左子节点大,则将当前 child 指针指向右子节点
            if (child + 1 < end && elem[child] > elem[child + 1]) {
                child++;
            }
            // 如果当前 child 节点的值小于父节点的值,则交换它们,并将 parent 更新为当前 child,继续下沉调整
            if (elem[child] < elem[parent]) {
                swap(child, parent);
                parent = child;
                // 更新 child 为新的左子节点索引
                child = 2 * parent + 1;
            } else {
                // 如果当前 child 节点的值不小于父节点的值,说明已满足堆的性质,结束调整
                break;
            }
        }
    }
  
}

测试一下

public class Prioirtyq {
    public static void main(String[] args){
        Pheap p=new Pheap(10);
        int arr[]={28,16,48,13,46,45,25,36,22,42};
        p.init(arr);
        p.creatHeapSmall();
        Pheap p1=new Pheap(10);
        p1.init(arr);
        p1.createHeapBig();
    }
}

可以看到,确实是所推的那样。

 3.建堆的时间复杂度

我们假设完全二叉树的高度为h,

那么,对于第一层,其结点只有一个,但是其需要向下调整h-1层。对于第二层,其节点有2^1个,每个结点需要向下调整的次数为h-2,以此类推,对于第h-1层,其拥有的节点有2^{h-2}个,但其属于倒数第二层,所以只需要向下调整1次。

那么对于一棵完全二叉树,要想将其建成一个堆,其时间复杂度就是每层的节点数*其向下调整的次数所需要花费的时间

T(n)=2^0*(h-1)+2^1*(h-2)+2^2*(h-3)+...+2^(h-2)*1               (1)式

我们不难看出,这是一个等差✖等比求和公式,我们可以用错位相减法来求出T(n),不难看出,其公比为2.

所以在(1)式左右两边同时✖2,得

2T(n)=              2^1*(h-1)+2^2*(h-2)+2^3*(h-3)+...+2^(h-1)*1     (2)式

(2)式-(1)式可得

T(n)=2^1+2^2+2^3+...+2^(h-1)+1-h

我们将1化为2^0,

T(n)=2^0+2^1+2^2+2^3+...+2^(h-1)-h

可以看出这是一个等比数列求和公式,根据求和公式Sn=a1*(1-q^n)/1-q,

T(n)=1*(1-2^h)/(1-2)-h=2^h-1-h

由二叉树的性质我们可以得到

节点数N=2^h-1

树的高度h=log2(N+1)

带入得

T(n)=N-log2(N+1)

根据大O渐进表示法

T(n)=O(N)

所以我们建堆的时间复杂度为O(N).

向下调整的时间复杂度为O(logN).

2.5堆的插入

在一个堆中,如果我们想插入一个数据,那么就在堆尾进行插入,再进行向上调整.

我们同时也需要考虑此时堆满了没

2.5.1向上调整

思路:对于插入的节点(我们称作目标节点)

         1.将目标节点与其父亲节点进行比较。

           大根堆:如果是大根堆,当父亲节点比目标节点小,那就目标节点和父亲节点进行互换后,将父亲节点的位置给到目标节点,接着继续进行向上调整。当父亲节点比目标节点大,停止向上调整。

          小根堆:当父亲节点比目标节点大,那就目标节点和父亲节点进行互换后,将父亲节点的位置给到目标节点,接着继续进行向上调整。当父亲节点比目标节点小,停止向上调整。

  我们以小根堆插入新节点为例:

我们用上述中所创建而成的小根堆,让其插入一个值为10的节点,如图

我们可以知道,新插入的节点其父亲节点是值为42的节点,明显比值为10目标节点要大,所以要进行互换,再进行向上调整。

最后我们可以得到:

 此时小根堆为{10,13,25,22,16,45,48,36,46,42}。

2.5.2堆插入代码实现:
public void pushInS(int val){
        // 判断堆是否已满
        if(isFull()){
            elem= Arrays.copyOf(elem,elem.length*2);
        }
        //进行插入
        elem[useSize++]=val;
        //进行向上调整
        sitUp(useSize-1);
    }
    public void sitUp(int child){
        int parent=(child-1)/2;
        while(child>=0){
            if(elem[child]<elem[parent]){
                swap(child,parent);
                child=parent;
                parent=(child-1)/2;
            }else{
                break;
            }
        }
    }
  /**
     * 检查堆是否已满。
     *
     * @return 堆是否已满的布尔值
     */
    public boolean isFull(){
        return useSize==elem.length;
    }

可以看到,确定是所推断的那样。 

2.6堆元素的删除

堆元素的删除一定是删除的堆顶元素!!!

2.6.1思路 

对顶元素的删除其实也是利用到向下调整。

1.将对顶元素与队尾的元素进行互换

2.让有效个数减1

3.再来一次向下调整

2.6.2.代码实现
public int Delete(){
        if(isEmpty()){
            throw new RuntimeException("堆为空");
        }
        int val=elem[0];
        swap(0,useSize-1);
        useSize--;
        sitDownSmall(0,useSize);
        return val;
    }

2.7获取堆的元素个数&&获取堆顶元素&&堆的打印

  public int size(){
        return useSize;
    }
    public int peek(){
        if(isEmpty()){
            throw new RuntimeException("堆为空");
        }
        return elem[0];
    }
    public void print(){
        for(int i=0;i<useSize;i++){
            System.out.print(elem[i]+" ");
        }
        System.out.println();
    }

3.常用接口特性

3.1PriorityQueue的特性

在java集合框架中,提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,但是PriorityQueue时线程不安全的,而PriorityBlockingQueue是线程安全的。

 我们在使用PriorityQueue时,需要导入相应的包

import java.util.PriorityQueue;

注意:

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

2. 不能插入null对象,否则会抛出NullPointerException

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

4. 插入和删除元素的时间复杂度为O(log2N).

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

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

 3.2PriorityQueue常用接口介绍

1.优先级队列的构造

常用的有以下几个:

 如果想要了解更多关于优先级队列,可以点击PriorityQueue (Java 平台 SE 8 ) (oracle.com)

 public static void main(String[] args){
        PriorityQueue<Integer> pq=new PriorityQueue<>();
        pq.offer(1);
        pq.offer(2);
        pq.offer(3);
        System.out.println(pq.poll());
        System.out.println(pq.peek());
    }

 扩容规则:
 如果容量小于64时,是按照oldCapacity的2倍方式扩容的

如果容量大于等于64,是按照oldCapacity的1.5倍方式扩容的

如果容量超过MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE来进行扩容。

数据结构的堆就先到这。

若有不足之处,欢迎指正~~

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

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

相关文章

Day06-Mybatis

1. Mybatis介绍 2. Mybatis连接数据库并返回数据事例 连接oracle数据的设置方式 spring.application.namespringboot-mybatis spring.datasource.driver-class-nameoracle.jdbc.OracleDriver spring.datasource.urljdbc:oracle:thin:192.168.100.66:1521:orcl spring.datasour…

每日一题《leetcode--59.螺旋矩阵 》

https://leetcode.cn/problems/spiral-matrix-ii/ 这道题跟我昨天发布的那道题一模一样&#xff0c;只需要注意这个矩阵是n*n。 文章代码如下&#xff1a; int** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {int** array (int**)malloc(sizeof(int*) *…

Python | Leetcode Python题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; class Solution:def generate(self, numRows: int) -> List[List[int]]:ret list()for i in range(numRows):row list()for j in range(0, i 1):if j 0 or j i:row.append(1)else:row.append(ret[i - 1][j] ret[i - 1][j - 1])ret…

HCIP-Datacom-ARST自选题库__BGP多选【22道题】

1.BGP认证可以防止非法路由器与BGP路由器建立邻居&#xff0c;BGP认证可以分为MD5认证和Keychain认证&#xff0c;请问以下哪些BGP报文会携带BCGP Keychain认证信息?(报头携带) open Update Notication Keepalive 2.传统的BGP-4只能管理IPv4单播路由信息&#xff0c;MP-B…

总线带宽(总线系统的数据传送速率)

定义 总线上每秒钟传输的最大字节数或比特数 表示方法 通常使用“比特率”来表示&#xff0c;单位为比特每秒&#xff08;bps&#xff0c;b/s&#xff09;。 计算公式 总线带宽总线宽度/传输周期 其中&#xff0c;总线宽度是指数据总线的位数&#xff08;单位&#xff1a…

GBB和Prob IoU[旋转目标检测理论篇]

在开始介绍YOLOv8_obb网络之前,需要先介绍一下arxiv.org/pdf/2106.06072 这篇文章的工作,因为v8_obb就是基于这篇论文提出的GBB和prob IoU来实现旋转目标检测的。 1.高斯分布 一维高斯分布的规律是中间高两边低,且当x为均值的时候取到最大值,表达式如下,标准正态分布图如…

数据库(10)——图形化界面工具DataGrip

以后关于数据库的图片演示就使用DataGrip了 : ) 创建数据库和表 在连接上数据库之后&#xff0c;可以选择Schema创建一个新的数据库。 点击OK后&#xff0c;就已经创建了一个空的表。 要在数据库中建立一张新的表&#xff0c;右键数据库&#xff0c;点击new table 要给新表添…

基于开源项目HAL STM32F4 +DSP库跑SVPWM开环速度测试

HAL STM32F4 ARM DSP库跑SVPWM开环速度测试 ✨本篇硬件电路和代码来源于此开源项目&#xff1a;https://github.com/MengYang-x/STM3F401-FOC/tree/main&#x1f4cd;硬件电路和项目介绍&#xff0c;立创开源广场&#xff1a;https://oshwhub.com/shadow27/tai-yang-neng-wu-re…

STL库--string

目录 string的定义 string中内存的访问 string常用函数实例解析 string的定义 定义string的方式跟基本类型相同&#xff0c;只需要在string后跟上变量名即可&#xff1a; string str; 如果要初始化&#xff0c;可以直接给string类型的变量进行赋值&#xff1a; string s…

Visual Studio 的使用

目录 1. 引言 2. 安装和配置 2.1 系统要求 2.2 安装步骤 2.3 初次配置 3. 界面介绍 3.1 菜单栏和工具栏 3.2 解决方案资源管理器 3.3 编辑器窗口 3.4 输出窗口 3.5 错误列表 3.6 属性窗口 4. 项目管理 4.1 创建新项目 4.2 导入现有项目 4.3 项目属性配置 5. 代…

stm32-DMA转运数据

在配置前要记得先定义一下DMA转运的源端数组和目标数组两个数组哦。 接下来我们就开始准备配置吧 配置 初始化 1.RCC开启时钟&#xff08;开启DMA的时钟&#xff09; void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState) 作用&#xff1a;开启时…

kafka-生产者发送消息消费者消费消息

文章目录 1、生产者发送消息&消费者消费消息1.1、获取 kafka-console-producer.sh 的帮助信息1.2、生产者发送消息到某个主题1.3、消费主题数据 1、生产者发送消息&消费者消费消息 1.1、获取 kafka-console-producer.sh 的帮助信息 [rootlocalhost ~]# kafka-console…

详解make file中的notdir

在 Makefile 中&#xff0c;$(notdir names…) 是一个函数&#xff0c;用于获取一组文件名或路径中的文件名部分&#xff0c;并将其返回。 这个函数通常用于从给定的路径中提取文件名部分&#xff0c;非常适合在 Makefile 中进行文件处理操作。 语法&#xff1a; makefile C…

基于单片机智能防触电装置的研究与设计

摘 要 &#xff1a; 针对潮湿天气下配电线路附近易发生触电事故等问题 &#xff0c; 对单片机的控制算法进行了研究 &#xff0c; 设 计 了 一 种 基 于 单片机的野外智能防触电装置。 首先建立了该装置的整体结构框架 &#xff0c; 再分别进行硬件设计和软件流程分析 &#xf…

从零开始写 Docker(十六)---容器网络实现(上):为容器插上”网线”

本文为从零开始写 Docker 系列第十六篇&#xff0c;利用 linux 下的 Veth、Bridge、iptables 等等相关技术&#xff0c;构建容器网络模型&#xff0c;为容器插上”网线“。 完整代码见&#xff1a;https://github.com/lixd/mydocker 欢迎 Star 推荐阅读以下文章对 docker 基本实…

邀请媒体参会,媒体邀约的正确打开方式

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 邀请媒体参会是一项重要的公关活动&#xff0c;需要细致的规划和执行。以下是一些步骤和建议&#xff0c;可以帮助你更有效地进行媒体邀约&#xff1a; 1. 拟定邀约媒体名单&#xff1a;…

启智CV机器人,ROS

资料&#xff1a; https://wiki.ros.org/kinetic/Installation/Ubuntu https://blog.csdn.net/qq_44339029/article/details/120579608 装VM。 装ubuntu20.04 desktop.iso系统。 装vm工具&#xff1a; sudo apt update sudo dpkg --configure -a sudo apt-get autoremove o…

可燃气体报警器检测周期:如何合理设定以满足安全需求?

可燃气体报警器作为工业安全和生产环境中不可或缺的安全防护设备&#xff0c;其准确性、稳定性和及时响应性对于防止火灾和爆炸事故具有重要意义。 因此&#xff0c;合理设定并严格执行可燃气体报警器的检测周期&#xff0c;是确保安全与可靠运行的核心环节。 一、检测周期的重…

轻兔推荐 —— 一个好用的软件服务推荐平台

给大家推荐一个好用的的软件服务推荐平台&#xff1a;轻兔推荐 网站界面简洁大方&#xff0c;没有太多杂七杂八的功能和页面&#xff0c;有明暗主题色可以选择&#xff0c;默认为亮色&#xff0c;可在网站上方手动切换。 每工作日都会推荐一款软件&#xff0c;有时会加更&…

如何理解Spring Boot自动配置原理和应用?

我们知道&#xff0c;基于Spring Boot&#xff0c;我们只需要在类路径中引入一组第三方框架的starter组件&#xff0c;就能在Spring容器中使用这些框架所提供的各项功能。这在当下的开发过程中已经习以为常&#xff0c;但在Spring Boot还没有诞生之前却是不可想象的。如果我们使…