【数据结构】堆(创建,调整,插入,删除,运用)

news2024/9/24 15:23:11

目录

堆的概念:

堆的性质:

堆的存储方式:

堆的创建 : 

堆的调整:

向下调整:

向上调整:

堆的创建:

建堆的时间复杂度:

 向下调整:

向上调整:

堆的插入与删除:

 堆的插入:

堆的删除:

堆的应用:

1.PriorityQueue的实现

2.堆排序:

3.Top-k问题 

结语:


堆的概念:

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

堆的性质:

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

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

具体如下图。 

 

堆的存储方式:

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

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

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

(1)如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2.

(2)如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子。

(3)如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子。

堆的创建 : 

为了使文章可读性更高下面给出测试堆类的基本代码,下面文章给出的代码均围绕这上面来。

elem:是一个类里面的数组(方便后序操作)

usedSize:是记录数组里面有多少个元素(不是数组容量)

TestHeap:构造方法为了简便把数组容量设为10,大小可以自己调整

initElem:初始化

public class TestHeap {
    public int[] elem;
    public int usedSize;
    public TestHeap(){
        elem = new int[10];
    }
    public void initElem(int[] array){
        for(int i = 0;i < array.length;i++){
            elem[i] = array[i];
            usedSize++;
        }
    }
}

堆的调整:

堆有向上调整向下调整两种调整方式,在创建时我们采用向下调整方式,这样的时间复杂度比较低。故下面主要讲解向下过程(以大堆为例) 步骤如下:

向下调整:

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

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

(1)parent右孩子是否存在,存在找到左右孩子中最小的孩子,让child进行标记。

(2)将parent与较小的孩子child比较,如果:

a:parent小于较小的孩子child,调整结束。

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

 以数组{ 27,15,19,18,28,34,65,49,25,37 }为例调整完变成。

对应的代码如下:

如果想要转换成小堆的话把大于号小于号改一改即可,故下面不在过多描述。

private void siftDown1(int parent,int end){
        int child = parent*2+1;
        while(child < end){
            if(child+1 < usedSize && elem[child] < elem[child+1]){
                child++;
            }
            if(elem[child] > elem[parent]){
                swap(child,parent);
                parent = child;
                child = parent*2+1;
            }else{
                break;
            }
        }
    }

 为了使代码整洁故再实现一个swap方法。

private void swap(int i,int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

注意:在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下调整。 时间复杂度分析:最坏的情况即图示的情况,从根一路比较到叶子,比较的次数为完全二叉树的高度,即时间复杂度为O(logn)log下标是以2为底的。

向上调整:

具体过程如图(按照插入的80走的路径):

代码如下:

先找新结点的parent的下标再child大于0的情况下循环进行比较交换,一旦发现不满足条件的立刻跳出循环,因为在使用向上调整之前堆已经排序好了。

public void siftUp(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;
            }
        }
    }

堆的创建:

如图所示是从最后一个结点的父亲结点开始遍历每一个结点都调用siftdown1进行向下调整,之后不断减减直到小于0(下标)。

代码如下:

 public void createBigHeap(){
        for(int parent = (usedSize-1-1)/2;parent >= 0;parent--){
            siftDown1(parent,usedSize);
        }
    }

运行结果:

通过观察elem的元素我们可以发现向下调整成功。💖

建堆的时间复杂度:

 向下调整:

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

向上调整:

如图:

经过比较我们选择时间复杂度较低的来进行堆的创建即向下调整,至于向上调整我们用于堆的堆的插入与删除。

堆的插入与删除:

 堆的插入:

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

(1)先将元素放入到底层空间中(注意:空间不够时需要扩容).

(2)将最后新插入的节点向上调整,直到满足堆的性质.

代码如下:

isFull用来判断是否需要扩容

public boolean isFull(){
        return usedSize == elem.length;
    }

siftUp用来向上调整,先找到新参数的parent结点, 传入siftup的参数为新offer进来的参数。

 public void offer(int val){
        //1.判断满
        if(isFull()){
            this.elem = Arrays.copyOf(elem,elem.length*2);

        }
        elem[usedSize] = val;
        usedSize++;
        siftUp(usedSize-1);
    }

运行结果如下:

我们可以发现成功增加数据并完成排序。

堆的删除:

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

(1) 将堆顶元素对堆中最后一个元素交换。

(2) 将堆中有效数据个数减少一个。

(3)对堆顶元素进行向下调整 。

 代码如下:

其实就是把最后一个元素的空间腾出来。

public int poll(){
        int tmp = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown1(0,usedSize);
        return tmp;
    }

运行结果如下:

可以看到65被成功删除并且数组的序列没有改变

堆的应用:

1.PriorityQueue的实现

可以使用堆来实现优先队列,由于java语言有自带的优先队列故这里不在实现给出它的常用方法直接调用即可。

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

2.堆排序:

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

1. 建堆

升序:建大堆

降序:建小堆

2. 利用堆删除思想来进行排序

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

具体过程如下:

代码后序会将8大排序整理成一篇排序专项。

3.Top-k问题 

TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都 不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1. 用数据集合中前K个元素来建堆

(1)前k个最大的元素,则建小堆.

(2)前k个最小的元素,则建大堆。

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

不要用Arrays.sort来做这道题,因为这是一道面试题,用sort就可以快速结束面试,回家等通知了。

top-k问题

使用堆的AC优化代码:

class Imp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1,Integer o2){
        return o2.compareTo(o1);
    }

}
class Solution {
    public int[] smallestK(int[] arr, int k) {
        Imp imp = new Imp();
        PriorityQueue<Integer> priorityqueue = new PriorityQueue<>(imp);
        int[] ans = new int[k];
        if(k == 0){
            return ans;
        }
        for(int i = 0;i < k; i++){
            priorityqueue.offer(arr[i]);
        }
        for(int i = k;i < arr.length; i++){
            int tmp = priorityqueue.peek();
            if(arr[i] < tmp){
                priorityqueue.poll();
                priorityqueue.offer(arr[i]);
            }
        }
        for(int i = 0;i < k; i++){
            ans[i] = priorityqueue.poll();
        }
        return ans;

    }
}

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

 

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

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

相关文章

FANUC机器人外部远程启动的相关参数设置示例

FANUC机器人外部远程启动的相关参数设置示例 如下图所示,在MENU---设置---选择程序中,设置程序选择模式:RSR(这个根据自己实际使用的自动启动方式来决定,你用RSR选RSR,用PNS就选PNS), 自动运行开始方法:选择UOP,即RSR1-RSR8的启动信号分别对应UI9-UI16, 最后,点击…

《合成孔径雷达成像算法与实现》Figure6.8

clc clear close all参数设置 距离向参数设置 R_eta_c 20e3; % 景中心斜距 Tr 2.5e-6; % 发射脉冲时宽 Kr 20e12; % 距离向调频率 alpha_os_r 1.2; % 距离过采样率 Nrg 320; % 距离线采样数 距离向…

【Linux】缓冲区与缓冲区的刷新策略

目录 1.缓冲区基础 1.1缓冲区的刷新策略 1.1.1三种刷新策略 1.1.2.两种强制刷新策略 2.用户级语言层缓冲区 2.1.默认在显示器输出 2.2.重定向到文件输出 2.3.write调用没有显示两份的原因 3.模拟实现文件缓冲区 3.1 myFileBuffer.h 3.2 myFileBuffer.c 4.系统内核缓…

flutter go_router 官方路由(一)基本使用

1 项目中添加最新的依赖 go_router: ^13.1.0如下图所示&#xff0c;我当前使用的flutter版本为3.16.0 然后修改应用的入口函数如下&#xff1a; import package:flutter/material.dart; import package:go_router/go_router.dart;void main() {runApp(const MyApp()); }cla…

【EEG信号处理】时频图与时频图的观察

非常快速和松散的介绍频谱和时频分析 当我们看到一个时频图时&#xff0c;我们应该考虑什么&#xff0c;应该有什么样的问题 什么是time-frequency plots 我们知道&#xff0c;左边是在时间维度上&#xff0c;根据电极的变化来绘制的折线图&#xff0c;他在时间维度上的&#…

Unity2D 学习笔记 0.Unity需要记住的常用知识

Unity2D 学习笔记 0.Unity需要记住的常用知识 前言调整Project SettingTilemap相关&#xff08;创建地图块&#xff09;C#脚本相关程序运行函数private void Awake()void Start()void Update() Collider2D碰撞检测private void OnTriggerStay2D(Collider2D player)private void…

react中hook封装一个table组件

目录 react中hook封装一个table组件依赖CommonTable / index.tsx使用组件效果 react中hook封装一个table组件 依赖 cnpm i react-resizable --save cnpm i ahooks cnpm i --save-dev types/react-resizableCommonTable / index.tsx import React, { useEffect, useMemo, use…

【数据结构】数据结构

本文是基于中国MOOC平台上&#xff0c;华中科技大学的《数据结构》课程和浙江大学的《数据结构》课程所作的一篇课程笔记&#xff0c;便于后期讲行系统性查阅和复习。 从个人角度出发&#xff0c;两个课程的讲解都有点不太易懂&#xff0c;好在多处可以互补&#xff0c;搭配进…

Backtrader 文档学习- Plotting -Plotting on the same axis

Backtrader 文档学习- Plotting -Plotting on the same axis 1.概述 在同一轴上绘图&#xff0c;绘图是在同一空间上绘制原始数据和稍微(随机)修改的数据&#xff0c;但不是在同一轴上。 核心代码&#xff0c;data数据正负50点。 # The filter which changes the close pri…

markdown加载自定义字体

写讲义&#xff0c;如果没有个像样 的字体多少有点难受。 最终的结果是劝退。 一、需要特定的markdown编辑器&#xff0c;我用的vscode 如果使用joplin、gitee的md文件是无法加载、渲染的。 二、 使用vscode想要渲染的话&#xff0c;似乎只能渲染一部分字体文件。下面不多…

【PyQt】09-控件提示信息、Lable标签

文章目录 前言一、控件提示信息1.1 代码1.2 解释 < b >在HTML标签中的作用1.3 添加按键后的代码运行结果 二、QLabel控件介绍2.1 内容2.2 常用的事件2.3 代码结果展示 总结 前言 1、控件提示信息 2、QLabel控件介绍 一、控件提示信息 关键点在于 效果如图所示&#x…

vFavorites

快速访问资产和文件夹的快捷方式 将您最常用的项目放入vFavorites中&#xff0c;以便立即访问 vFavorites中的项目与项目选项卡中的常规项目类似&#xff1a; - 单击文件夹以打开它 - 单击资产以选择它 - 双击脚本以进行编辑 - 拖放材料或预制件以将其添加到场景中 下载&#…

Qt网络编程-TCP与UDP

网络基础 TCP与UDP基础 关于TCP与UDP的基础这里就不过多介绍了&#xff0c;具体可以查看对应百度百科介绍&#xff1a; TCP&#xff08;传输控制协议&#xff09;_百度百科 (baidu.com) UDP_百度百科 (baidu.com) 需要知道这两者的区别&#xff1a; 可靠性&#xff1a; TC…

部署一个在线OCR工具

效果 安装 1.拉取镜像 # 从 dockerhub pull docker pull mmmz/trwebocr:latest 2.运行容器 # 运行镜像 docker run -itd --rm -p 10058:8089 --name trwebocr mmmz/trwebocr:latest 使用 打开浏览器输入 http://192.168.168.110:10058/ 愉快滴使用吧

五官行为检测(表情基)解决方案提供商

随着人工智能技术的日益成熟&#xff0c;情感识别与行为分析在企业界的应用逐渐广泛。美摄科技作为业内领先的五官行为检测&#xff08;表情基&#xff09;解决方案提供商&#xff0c;致力于为企业提供高效、精准的情感识别与行为分析服务。 美摄科技的五官行为检测&#xff0…

【网页设计期末】茶文化网站

本文资源&#xff1a;https://download.csdn.net/download/weixin_47040861/88818886 1.题目要求 设计要求&#xff1a; &#xff08;1&#xff09;网站页面数量不少于4个&#xff0c;文件命名规范&#xff0c;网站结构要求层次清楚&#xff0c;目录结构清晰&#xff0c;代码…

阿里云服务器多少钱?2024年阿里云服务器租用费用表大全

2024年阿里云服务器租用价格表更新&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…

LabVIEW动平衡测试与振动分析系统

LabVIEW动平衡测试与振动分析系统 介绍了利用LabVIEW软件和虚拟仪器技术开发一个动平衡测试与振动分析系统。该系统旨在提高旋转机械设备的测试精度和可靠性&#xff0c;通过精确测量和分析设备的振动数据&#xff0c;以识别和校正不平衡问题&#xff0c;从而保证机械设备的高…

【状态管理一】概览:状态使用、状态分类、状态具体使用

文章目录 一. 状态使用概览二. 状态的数据类型1. 算子层面2. 接口层面2.1. UML与所有状态类型介绍2.2. 内部状态&#xff1a;InternalKvState 将知识与实际的应用场景、设计背景关联起来&#xff0c;这是学以致用、刨根问底知识的一种直接方式。 本文介绍 状态数据管理&#x…

闭区间上连续函数的性质【高数笔记】

1. 分几个性质 2. 每个性质的注意事项是什么 3. 每个性质适用什么类型的题型 4. 注意最值定理和正弦函数的不同 5. 做题步骤是什么