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

前言

在上一章我们讲了二叉树,这一节我们来讲堆(优先级队列),所以想知道堆创建,可以看一下二叉树的一些简单概念。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来进行扩容。

数据结构的堆就先到这。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

补环境——A股市场

补环境 吐环境 1.Proxy对象 Proxy对象由两个部分组成&#xff1a;target、handler target:目标对象 handler&#xff1a;是一个对象&#xff0c;声明了代理target的指定行为&#xff0c;支持的拦截操作&#xff0c;一共13种&#xff1a; get(target,propKey,receiver)&…

【全开源】酒店订单管理系统源码(FastAdmin+ThinkPHP)

一款基于FastAdminThinkPHP开发的旨在为民宿、酒店、宾馆等提供房态、订单、财务、客史等数据化、信息化的智慧管理工具&#xff0c;实现一站式订房管理&#xff0c;帮助酒店、民宿、宾馆提升管理效率&#xff0c;降低管理成本&#xff0c;提升行业竞争力。 打造高效、便捷的酒…

为什么c语言不对0和NULL做严格的区分?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「c语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;这个答案很简单:c语言不区分…

ubuntu中idea创建spark项目步骤

1.前置条件 ubuntu中已经安装idea,jdk,scala,spark 2.打开idea&#xff0c;新建&#xff0c;选择Maven项目 3.在IDEA中&#xff0c;File-Setting-Plugin&#xff0c;下载Scala插件 4.File-project structure&#xff0c;导入插件 4.1在全局库中&#xff0c;选择导入刚才的sca…

HTML 页面布局

慢慢生活&#xff0c;慢慢变好 —— 24.5.28 页面布局 盒子: 页面中所有的元素(标签)&#xff0c;都可以看做是一个盒子&#xff0c;由盒子将页面中的元素包含在一个矩形区域内&#xff0c;通过盒子的视角更方便的进行页面布局 盒子模型组成: 内容区域(content)、内边距区域(pa…

什么是知识中台?为什么企业需要知识中台?

如今市面上的企业数不胜数&#xff0c;企业的任何一个小细节都会产生很大的影响。近几年来一直很热门的知识中台备受企业关注。关于如何高效地管理、整合和运用知识&#xff0c;成为了每一家企业都在重点关注的问题。而知识中台&#xff0c;就是为了解决这一问题而诞生的一个全…

鸿蒙开发接口UI界面:【@ohos.router (页面路由)】

页面路由 说明开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。页面路由需要在页面渲染完…

如何在生产环境中以非 Root 用户启动 Kafka

目录 如何在生产环境中以非 Root 用户启动 Kafka1. 创建 Kafka 用户2. 设置目录权限3. 配置 systemd 服务文件4. 启动和启用 Kafka 服务5. 验证 Kafka 服务经验总结 为了在生产环境中以非 root 用户&#xff08;如 kafka 用户&#xff09;启动 Kafka&#xff0c;您需要确保 Ka…

Unity之如何使用Localization来实现文本+资源多语言

前言 使用Unity实现本地化&#xff08;Localization&#xff09;功能 在当今的游戏开发中&#xff0c;支持多语言已成为一项基本需求。Unity作为主流的游戏开发引擎&#xff0c;提供了强大的本地化工具&#xff0c;使开发者能够方便地为游戏添加多语言支持。本文将介绍如何在U…

Linux 防火墙 firewalld 常用命令

1 防火墙 - firewalld 1.1 开启防火墙 # 临时性开启&#xff0c;服务器重启后会恢复为原来的状态 systemctl start firewalld # 永久性开启&#xff08;即开机启动&#xff09;&#xff0c;重启服务器后生效 systemctl enable firewalld1.2 关闭防火墙 # 临时性关闭&#xf…

Neural Filters:照片恢复

Ps菜单&#xff1a;滤镜/Neural Filters/恢复/照片恢复 Neural Filters/RESTORATION/Photo Restoration 照片恢复 Photo Restoration借助 AI 强大功能快速恢复旧照片&#xff0c;提高对比度、增强细节、消除划痕。将此滤镜与着色相结合以进一步增强效果。 “照片恢复”滤镜利用…

Vue3 之 动态组件和KeepAlive组件

一、动态组件 1、简介 ​ 在某些业务场景下&#xff0c;页面的某模块具有多个组件但在同一时间只显示一个&#xff0c;需要在多个组件之间进行频繁的切换&#xff0c;如&#xff1a;tab切换等场景。除了可以使用v-if、v-show根据不同条件显示不同组件之外&#xff0c;还可以通…

Sora,数据驱动的物理引擎

文生视频技术 Text-to-Video 近日&#xff0c;Open AI发布文生视频模型Sora&#xff0c;能够生成一分钟高保真视频。人们惊呼&#xff1a;“真实世界将不再存在。” Open AI自称Sora是“世界模拟器”&#xff0c;让“一句话生成视频”的AI技术向上突破了一大截&#xff0c;引…

AI早班车5.22

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d; 一位上进心十足的【Java ToB端大厂…

Linux:进程控制(二.详细讲解进程程序替换)

上次讲了&#xff1a;Linux&#xff1a;进程地址空间、进程控制&#xff08;一.进程创建、进程终止、进程等待&#xff09; 文章目录 1.进程程序替换1.1概念1.2原理1.3使用一个exec 系列函数execl&#xff08;&#xff09;函数结论与细节 2.多进程时的程序替换3.其他几个exec系…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-24.3,4 SPI驱动实验-I.MX6U SPI 寄存器

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

Vue2基本创建项目

简单版项目初始化 新建一个vue2 官网文档&#xff1a;介绍 — Vue.js 先确保下载了vue的脚手架 npm install -g vue-cli npm install -g vue/cli --force vue -V 创建项目 vue create 自己起个名字 选择自己选择特性 选择&#xff1a; Babel&#xff1a;他可以将我们写…

基于模糊PID控制器的汽车电磁悬架控制系统simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于模糊PID控制器的汽车电磁悬架控制系统simulink建模与仿真。 2.系统仿真结果 上面的仿真结果是无控制器和LQG的对比&#xff0c;以及有控制器和LQG的对比仿真。 3.核心程…

UE5 UE4 快速定位节点位置

在材质面板中&#xff0c;找到之前写的一个节点&#xff0c;想要修改&#xff0c;但是当时写的比较多&#xff0c;想要快速定位到节点位置. 在面板下方的 Find Results面板中&#xff0c;输入所需节点&#xff0c;找结果后双击&#xff0c;就定位到该节点处。 同理&#xff0c;…

从0开始学会做标书:新手学习做标书制作必修(95节课)

入门框架 电子标书 商务标书 文档排版 技术标书 实操演示 你是否也有同样的问题 1、做标书公司没人教、没人带? 2、如何看懂招标文件? 3、小白零基础能不能学习做标书? 4、商务标、技术标如何得高分? 5、做标书需要什么软件? 6、如何制作电子标书? 7、如何避…