集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列

news2024/11/16 11:51:55

系列文章目录

集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列

优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列

  1. 优先级队列的概念
  2. 堆的概念
  3. 堆的存储方式
  4. 堆的创建
  5. 变量的作用域和生命周期
  6. 用堆模拟实现优先级队列

文章目录

  • 系列文章目录
    • 集合及数据结构第十节(上)————优先级队列,堆的创建、插入、删除与用堆模拟实现优先级队列
  • 一、优先级队列
    • 1.优先级队列的概念
  • 二、优先级队列的模拟实现
    • 1. 堆的概念
    • 2. 堆的存储方式
    • 3. 堆的创建
      • 堆向下调整
      • 建堆的时间复杂度
    • 5.变量的作用域和生命周期( * * *
      • 堆的插入
      • 堆的删除
    • 6.用堆模拟实现优先级队列
      • 常见习题:


一、优先级队列

1.优先级队列的概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

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

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

1. 堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1Ki<= K2i+2 (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. 堆的创建

堆向下调整

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

在这里插入图片描述
仔细观察上图后发现:根节点的左右子树已经完全满足堆的性质因此只需将根节点向下调整好即可

大根堆:
在这里插入图片描述
向下调整过程思路:

  1. 从最后一棵子树(下标为 9 的节点)开始调整
  2. 找到左右孩子的最大值并与根节点进行比较,如果比根节点大,那么就交换。
  3. 知道当前子树的根节点下标,就能知道下一棵需要调整的子树,为当前节点下标 - 1
  4. 一直调整到 0 下标这颗树时停止调整

要完成代码存在的问题:

  1. 每颗子树在调整的时候,什么时候能结束这颗子树的调整。
  • 定义树的节点个数为len(数组长度),当当前下标为 i 的子树的左孩子下标 2 * i + 1 > len或者右孩子下标2 * i + 2 > len时,该子树调整完毕了
  1. 最后一棵子树的根节点下标怎么确定。

向下调整过程(以大根堆为例):

  1. 让parent标记需要调整的节点,child标记parent的左孩子(注意:parent如果有孩子一定先是有左孩子)
  2. 如果parent的左孩子存在,即:child < usedSize, 进行以下操作,直到parent的左孩子不存在
  • parent右孩子是否存在,存在找到左右孩子中最大的孩子,让child进行标

  • 将parent与较大的孩子child比较,如果:

  • parent小于较大的孩子child,调整结束

  • 否则:交换parent与较大的孩子child,交换完成之后,parent中小的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent
    = child;child = parent*2+1; 然后继续步骤2的操作。

在这里插入图片描述
在这里插入图片描述
完整代码:

public class TestHeap {
    private  int[] elem;//用来存完全二叉树的数组
    public int usedSize;//用来记录当前堆中有效的数据个数

    public TestHeap(){//构造方法
        this.elem = new  int[10];//初始化数组大小为10
    }

    public void initElem(int[] array){//初始化elem数组
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;//拷贝一个有效数据加1
        }
    }

    public void createHeap(){//创建大根堆
        //usedSize - 1 -->len   //usedSize - 1 - 1 -->拿到最后一个孩子节点(9)下标  //(usedSize - 1 - 1) / 2 -->拿到该孩子节点的父亲节点
        //如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
        for (int parent = (usedSize - 1 - 1) / 2;parent >= 0;parent--){//确定每棵子树parent的下标
            siftDown(parent,usedSize);//每棵子树向下调整,传参为每颗子树的根和结束的位置
        }
    }

    public void siftDown(int parent,int len){//每棵子树向下调整
        int child = 2 * parent + 1;//parent节点的左孩子下标为2 * i + 1
        while (child < len){//当至少有左孩子时
            //在进行比较的时候要保证child + 1 < len,否则就会越界比较
            if (child + 1 < len && elem[child] < elem[child + 1]){//左孩子和右孩子进行比较,如果右孩子的值大,那么就记录一下它的下标
                child = child + 1;
            }
            //走完上述if语句,则child下标一定保存的是左右两个孩元素最大值的下标
            if (elem[child] > elem[parent]){//child下标的元素大于parent下标的元素,进行交换
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;//parent指向child的位置
                child = 2 * parent + 1;//再接着对child的右孩子进行相同操作,parent节点的左孩子下标为2 * i + 1
            }else{
                break;//不需要比较了,直接break退出循环
            }
        }
    }
}

建堆的时间复杂度

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

在这里插入图片描述

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

5.变量的作用域和生命周期( * * *

堆的插入

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

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质
    在这里插入图片描述
    向上调整:
  3. 将新增节点child于其父亲节点parent进行比较,如果大于父亲节点parent就与其交换
  4. 交换结束后调整child和parent的位置

代码实现:

    public void push(int val){//堆的插入
        //满了时
        if (isFull()){
            elem = Arrays.copyOf(elem,elem.length * 2);//二倍扩容
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);

        usedSize++;//插入后usedSize加一

    }

    public void swap(int child,int parent){
        int temp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = temp;
    }
    public boolean isFull(){
        return usedSize == elem.length;//当usedSize和数组的长度相等时,说明满了
    }
    public void siftUp(int child){//向上调整
        while (child > 0){
            int parent = (child - 1) / 2;//如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
            if (elem[usedSize] > elem[parent]){//如果大于父亲节点parent就与父亲节点parent交换
                swap(child,parent);//交换
                child = parent;//当前child指向parent的位置
                parent = (child - 1) / 2;
            }else {//如果小于父亲节点parent,说明该子树已经是大根堆,break跳出循环
                break;
            }
        }
    }
}

补充:
向上调整也可以建堆但是时间复杂度会比较高
在这里插入图片描述

堆的删除

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

  1. 将堆顶元素对堆中最后一个元素交换
  2. 将堆中有效数据个数减少一个
  3. 对堆顶元素进行向下调整
    在这里插入图片描述
public class heapEmptyWrong extends RuntimeException{//堆为空的异常
    public heapEmptyWrong(String message) {
        super(message);
    }
}
  public void swap(int child,int parent){//交换
        int temp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = temp;
    }
public void siftDown(int parent,int len){//每棵子树向下调整
        int child = 2 * parent + 1;//parent节点的左孩子下标为2 * i + 1
        while (child < len){//当至少有左孩子时
            //在进行比较的时候要保证child + 1 < len,否则就会越界比较
            if (child + 1 < len && elem[child] < elem[child + 1]){//左孩子和右孩子进行比较,如果右孩子的值大,那么就记录一下它的下标
                child = child + 1;
            }
            //走完上述if语句,则child下标一定保存的是左右两个孩元素最大值的下标
            if (elem[child] > elem[parent]){//child下标的元素大于parent下标的元素,进行交换
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;//parent指向child的位置
                child = 2 * parent + 1;//再接着对child的右孩子进行相同操作,parent节点的左孩子下标为2 * i + 1
            }else{
                break;//不需要比较了,直接break退出循环
            }
        }
    }
    
    
 public int pop(){//删除堆的元素
        //判断堆是否为空
        if (isEmpty()){//抛出异常
            throw new  heapEmptyWrong("当前堆为空,不能进行删除操作");
        }
        int oldVal = elem[0];//记录对顶元素
        swap(0,usedSize - 1);//堆顶元素对堆中最后一个元素交换
        usedSize--;
        siftDown(0,usedSize);//0-usedSize的元素进行向下调整
        return oldVal;
    }

6.用堆模拟实现优先级队列

	public class heapEmptyWrong extends RuntimeException{//堆为空的异常
	    public heapEmptyWrong(String message) {
	        super(message);
	    }
	}
  public void swap(int child,int parent){//交换
        int temp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = temp;
    }
	public void siftDown(int parent,int len){//每棵子树向下调整
        int child = 2 * parent + 1;//parent节点的左孩子下标为2 * i + 1
        while (child < len){//当至少有左孩子时
            //在进行比较的时候要保证child + 1 < len,否则就会越界比较
            if (child + 1 < len && elem[child] < elem[child + 1]){//左孩子和右孩子进行比较,如果右孩子的值大,那么就记录一下它的下标
                child = child + 1;
            }
            //走完上述if语句,则child下标一定保存的是左右两个孩元素最大值的下标
            if (elem[child] > elem[parent]){//child下标的元素大于parent下标的元素,进行交换
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;//parent指向child的位置
                child = 2 * parent + 1;//再接着对child的右孩子进行相同操作,parent节点的左孩子下标为2 * i + 1
            }else{
                break;//不需要比较了,直接break退出循环
            }
        }
    }
     public void siftUp(int child){//向上调整
        while (child > 0){
            int parent = (child - 1) / 2;//如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
            if (elem[usedSize] > elem[parent]){//如果大于父亲节点parent就与父亲节点parent交换
                swap(child,parent);//交换
                child = parent;//当前child指向parent的位置
                parent = (child - 1) / 2;
            }else {//如果小于父亲节点parent,说明该子树已经是大根堆,break跳出循环
                break;
            }
        }
    }
public class MyPriorityQueue {
	private int[] array = new int[100];
	private int size = 0;
	public void push(int val){//堆的插入
        //满了时
        if (isFull()){
            elem = Arrays.copyOf(elem,elem.length * 2);//二倍扩容
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);
        usedSize++;//插入后usedSize加一
    }

	public int pop(){//删除堆的元素
        //判断堆是否为空
        if (isEmpty()){//抛出异常
            throw new  heapEmptyWrong("当前堆为空,不能进行删除操作");
        }
        int oldVal = elem[0];//记录对顶元素
        swap(0,usedSize - 1);//堆顶元素对堆中最后一个元素交换
        usedSize--;
        siftDown(0,usedSize);//0-usedSize的元素进行向下调整
        return oldVal;
    }
    
	public int peek() {
	return elem[0];
	}
}

常见习题:

1.下列关键字序列为堆的是:( A )

A: 100,60,70,50,32,65

B: 60,70,65,50,32,100

C: 65,100,70,32,50,60

D: 70,65,100,32,50,60

E: 32,50,100,70,65,60

F: 50,100,70,65,60,32
在这里插入图片描述
2.已知小根堆为8,15,10,21,34,16,12,删除关键字8之后需重建堆,在此过程中,关键字之间的比较次数是( C )

A: 1

B: 2

C: 3

D: 4
在这里插入图片描述
4.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是( C )

A: [3,2,5,7,4,6,8]

B: [2,3,5,7,4,6,8]

C: [2,3,4,5,7,8,6]

D: [2,3,4,5,6,7,8]

在这里插入图片描述

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

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

相关文章

谷粒商城实战笔记-250-商城业务-消息队列-RabbitMQ安装-Docker

一&#xff0c;docker安装RabbitMq RabbitMQ 是一个开源的消息代理软件&#xff0c;广泛用于实现异步通信和应用程序解耦。 使用 Docker 容器化技术可以简化 RabbitMQ 的安装和部署过程。 以下是使用 Docker 安装 RabbitMQ 的详细步骤。 步骤 1: 安装 Docker 如果您的系统…

Linux 软件编程 网络 tcp

1.TCP粘包问题&#xff1a; TCP发送数据是连续的&#xff0c;两次发送的数据可能粘连成一包被接收到 1.解决粘包问题方法&#xff1a; 1.接收指定长度&#xff1a;&#xff08;不稳定&#xff09; 发送5个字节 接收5个字节 2.睡眠&#x…

【数据库】Mysql 批量变更所有字段类型为varchar的字符集

生成变更语句 SELECT CONCAT(ALTER TABLE , TABLE_NAME, MODIFY , COLUMN_NAME, , COLUMN_TYPE, , CHARACTER SET utf8 COLLATE utf8_general_ci , CASE WHEN IS_NULLABLE YES THEN NULL DEFAULT NULL WHEN IS_NULLABLE NO AND ISNULL(COLUMN_DEFAULT) THEN NOT NULL EL…

Adobe Illustrator矢量绘图软件win/mac软件下载安装

一、软件概述 1.1 Adobe Illustrator简介 Adobe Illustrator是一款由Adobe Systems开发的强大矢量绘图软件&#xff0c;专为设计师、艺术家及图形专家设计。它广泛应用于平面设计、插画、UI设计、图标设计、排版及数字媒体制作等领域。Illustrator以其独特的矢量图形处理能力…

Datawhale X 魔搭 AI夏令营第四期 | AIGC文生图——进阶上分 实战优化 Task3笔记

Hi&#xff0c;大家好&#xff0c;我是半亩花海。在上一个任务中&#xff0c;我们逐行精读baseline&#xff0c;掌握了利用AI工具提升学习效率&#xff0c;并制作了话剧连环画&#xff0c;初步了解Secpter WebUI。今天&#xff0c;我们将深入探讨微调的基本原理及其参数&#x…

海外版多语言互助盘三三复制超级人脉系统

此套源码是全新二开的超级人脉系统&#xff0c;面向海外操作新增多语言&#xff0c;后台可新增其他语言.

【图机器学习系列】(二)从传统机器学习角度理解图(一)

微信公众号&#xff1a;leetcode_algos_life&#xff0c;代码随想随记 小红书&#xff1a;412408155 CSDN&#xff1a;https://blog.csdn.net/woai8339?typeblog &#xff0c;代码随想随记 GitHub: https://github.com/riverind 抖音【暂未开始&#xff0c;计划开始】&#xf…

java 中的设计模式

文章目录 一、前言二、设计模式的分类三、设计模式的原则1、开闭原则&#xff08;Open Close Principle&#xff09;2、里氏代换原则&#xff08;Liskov Substitution Principle&#xff09;3、依赖倒转原则&#xff08;Dependence Inversion Principle&#xff09;4、接口隔离…

【案例55】WebSphere非root用户启动方案

问题背景 很多项目为了安全因素考虑&#xff0c;想让在Linux服务器中启动的程序都用非root用户启动。 解决方案 创建用户和组 现在我们用 root 用户登录&#xff0c;并创建用户和组。 ##创建用户 [rootnc-test ~]# useradd wasadmin##修改密码 [rootnc-test~]# passwd was…

AT 指令和WIFI模组

此次使用到的wifi模组是乐鑫的wifi模组esp8684&#xff0c;该wifi模组内部集成了MQTT协议 串口发送AT指令与单片机进行通信&#xff0c;一下是ESP8684的管脚布局。 ESP8684管脚描述&#xff1a; 注&#xff1a;30 和 31 号管脚是用于调试的管脚&#xff0c;20 与 21 号管脚才是…

allure实现测试报告的生成

在测试用例编写完成之后&#xff0c;我们可以通过allure生成测试报告 一、配置java环境变量&#xff0c;jdk17的版本 二、安装allure-commanlie工具&#xff0c;可在官网下载&#xff0c;也可通过百度网盘下载 链接: https://pan.baidu.com/s/10123Iv2f7Ht476feDiP0Yw 提取码…

视频智能分析平台烟火检测视频安防监控烟火算法识别应用方案

烟火检测算法的应用方案主要围绕其核心技术——深度学习&#xff08;特别是卷积神经网络CNN&#xff09;和计算机视觉技术展开&#xff0c;旨在实现对监控视频中的烟雾和火焰进行实时、准确的检测与识别。以下是一个详细的烟火检测算法应用方案&#xff1a; 一、技术原理 烟火…

如何使用Python进行餐馆满意度分析——K-means算法与NLP情感分析实战

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

TOP10漏洞原理

## 本人为学习网安不久的新人&#xff0c;记一次学习笔记&#xff0c;有缺陷或者表述不对的地方欢迎大家指出&#xff0c;感谢&#xff01; ## 1、sql注入&#xff1a;web应用程序对用户输入的数据没有进行过滤&#xff0c;或者过滤不严&#xff0c;就把sql语句拼接进数据库…

【C++ Primer Plus习题】3.2

问题: 解答: #include <iostream> using namespace std;const int FOOT_TO_INCH 12; const double INCH_TO_MI 0.0254; const float KG_TO_POUND 2.2;int main() {int inch 0;int foot 0;int inchs 0;double mi 0;int pound 0;double kg 0;double BMI 0;cout &…

QEMU/KVM 虚拟机显卡透传 (vfio-pci)

首发日期 2024-08-22, 以下为原文内容: 本文介绍将 PCIE 设备 (显卡) 透传给 QEMU/KVM 虚拟机的一种方法, 基于 Linux 内核的 vfio-pci 功能. 透传 (pass through) 之后, 虚拟机内可以直接操作 (使用) 显卡硬件, 就像物理机那样, 几乎没有虚拟化的性能损失. 这里是 穷人小水滴…

电商分账系统整个原理 空中分账具体解决方案

电商分账系统最近比较火&#xff0c;比如大家做天猫的时候&#xff0c;直接可以通过网商贷把钱拿出来&#xff0c;但抖店没有这种贷款&#xff0c;大家都有一种不想进对公的方式&#xff0c;那系统本身就是虚拟户加操作系统&#xff0c;虚拟户是银行开的&#xff0c;银行开就需…

【Java】从零到一使用Feign与Sentinel (详细图解)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 认识依赖4.1.1 Feign依赖4.1.2 Sentinel依赖4.1.3 负载均衡依赖4.2 父子项目…

基于 Java Web 的校园驿站管理系统

TOC ssm016基于 Java Web 的校园驿站管理系统jsp 第1章 绪论 1.1 课题背景 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。…

嵌入式学习(TCP通信和抓包)

粘包问题&#xff1a; TCP协议在传输时&#xff0c;可能会遇到粘包的问题。造成这个问题的原因&#xff0c;是因为tcp流式套接字&#xff0c;数据之间没有边界造成的。导致了多次的数据&#xff0c;粘黏在一起。 解决方法&#xff1a; 1、规定一些数据与数据之间的间隔符 2、…