【数据结构】堆

news2025/2/23 12:48:40

堆的定义

(最大)堆是一个可以被看成一棵树的数组对象,满足如下性质:

  • 堆中的父亲结点总大于或等于其左右孩子结点的值
  • 总是一棵完全二叉树

完全二叉树

  • 若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
    在这里插入图片描述
    在这里插入图片描述
  • 根据堆的性质可以得出如下结论
    • 根节点没有父亲结点
    • 除根节点之外的任意结点(i)的父亲结点的索引为: parent = i/2
    • 任意结点的左孩子结点的索引为: leftIndex = 2 * i
    • 任意结点的右孩子结点的索引为: rightIndex = 2 * i +1
      上面的结论是根结点存储在索引为1的位置,如果根结点存储在索引为0的位置时,会得到:
    • parent = (i- 1)/2;
    • leftIndex = 2 * i + 1
    • rghtIndex = 2 * I + 2

向堆中添加元素(上浮 float up)

1、添加元素52,添加到数组中索引为size的位置,然后更新size
在这里插入图片描述

2、从最后一个结点开始与父亲结节进行(优先级)比较,如果父亲结点的优先级低于当前结点,
在这里插入图片描述
则进行交换
在这里插入图片描述
3、重复第二步操作,
在这里插入图片描述
在这里插入图片描述

4、直至根节点或父亲结点的优先级高于当前结点
在这里插入图片描述

取出堆中优先级最高的元素(下沉 swim)

最大堆中优先级最高的元素是索引为0的元素
在这里插入图片描述

1、使用最后一个元素替换索引为0元素,更新size
在这里插入图片描述

2、从索引为0的位置开始进行下沉操作
下沉操作:
1>找到当前结点左右孩子结点中优先级较高的结点
在这里插入图片描述

2>如果当前结点的优先级小于左右孩子结点中优先较高的结点,则进行交换
在这里插入图片描述

3>重复第2步操作
在这里插入图片描述
在这里插入图片描述

3、直至叶子结点或左右孩子结点中优先级较高结点小于当前结点的优先级
在这里插入图片描述

堆的时间复杂度分析

从上面的分析图中,可以得出:无论进行上浮还是下沉操作,最多交换的次数为整棵树的高度h
O(h) = O(logn)

Heapify和Replace

replace: 取出最大元素后,放入一个新元素
实现方式:直接将堆顶元素替换成新元素,然后进行Sift down操作
heapify: 将任意数组整理成堆的形状
实现方式:从最后一个元素的父亲结点开始进行调整(sift down),直到根结点
在这里插入图片描述
1、找到最后一个元素的父亲结点。 (size-1-1)/2在这里插入图片描述
2、循环进行下沉操作,直至根结点
在这里插入图片描述
在这里插入图片描述

上面分析得知,将一个元素插入到堆中,复杂度为O(logn),所以,将n个元素逐个插入
到一个空堆中,算法复杂堆为O(nlogn)

heapify的过程:

在这里插入图片描述

堆的代码实现

import java.security.PublicKey;
import java.util.Arrays;
import java.util.Random;

/**
 * 最大堆 优先级-> 数值大的优先级高
 * 最小堆 优先级-> 数值小的优先级高
 */
public class MaximumHeap<T extends Comparable<T>> {
    private T[] data; // 保存树的结点
    private int size; //保存树中结点的个数

    public MaximumHeap() {
        this.data = (T[]) new Comparable[500];
        this.size = 0;
//        Arrays.fill(data, Integer.MIN_VALUE);
    }

    //重写构造  构建一棵完全二叉树
    public MaximumHeap(T[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        this.data = (T[]) new Comparable[arr.length + 1];
        for (int i = 0; i < arr.length; i++) {
            this.data[i + 1] = arr[i];
        }
        this.size = arr.length;
        this.heapify(); //构建堆
    }

    //判断堆是否为空
    public boolean isEmpty() {
        return this.size == 0;
    }

    //获取堆中元素的个数
    public int getSize() {
        return this.size;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        int cnt = 0;
        while (cnt < this.size) {
            sb.append(this.data[cnt + 1]);
            if (cnt != this.size - 1) {
                sb.append(",");
            }
            cnt++;
        }
        sb.append("]");
        return sb.toString();
    }

    //根据当前结点所在数组的索引 得到 父结点的索引
    public int getParentIndex(int index) {
        if (index == 1) { // 处理根节点
            return -1;
        }
        if (index < 0) {
            throw new IllegalArgumentException("index is invalid!");
        }
        return index / 2;
    }

    //根据当前结点所在的数组的索引获取左孩子的结点
    public int getLeftChildIndex(int index) {
        return 2 * index;
    }

    //向树中添加元素(构建堆)
    public void add(T val) {
        //1、将size++
        this.size++;
        //2、将val添加到数组中,索引为size 的位置
        this.data[this.size] = val;
        //3、上浮操作
//        floatUp();
        floatUp2(val);
    }

    // 上浮操作
    private void floatUp() {
        //1、从最后一个结点开始进行上浮操作
        int curIndex = this.size;
        int parentIndex = getParentIndex(curIndex);

        //2、使用当前结点与父结点的优先级做比较,当前结点的优先级>父结点的优先级,就进行交换
        //3、直到根节点或当前结点的优先级<=父结点的优先级
        while (curIndex > 1 && this.data[curIndex].compareTo(this.data[parentIndex]) > 0) {
            swap(curIndex, parentIndex);
            // 更新当前结点的索引,继续判断并交换
            curIndex = parentIndex;
            // 重新得到父亲结点
            parentIndex = getParentIndex(curIndex);
        }
    } // 用交换的策略

    private void floatUp2(T ele) { // 用替换的策略
        //1、从最后一个结点开始进行上浮操作
        int curIndex = this.size;
        int parentIndex = getParentIndex(curIndex);

        while (curIndex > 1 && this.data[parentIndex].compareTo(ele) < 0) {
            //使用当前结点和父结点优先级进行比较,当当前结点的优先级>父结点优先级 父结点替换当前结点
            this.data[curIndex] = this.data[parentIndex];
            curIndex = parentIndex;
            parentIndex = getParentIndex(curIndex);
        }
        this.data[curIndex] = ele;
    }

    //下沉操作
    private void siftDown() {
        //下沉操作 <1>记录下沉结点的索引
        int curIndex = 1;
        //<2>获取下沉结点左右孩子的索引
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex; // 用来保存左右孩子中优先级较高的结点索引
        //判断左孩子是否存在
        while (leftChildIndex < this.size) { //此时存在左孩子
            int rightChildIndex = leftChildIndex + 1;
            // 判断右孩子是否存在 并且 右孩子结点的优先级大于左孩子结点的优先级
            if (rightChildIndex <= this.size && this.data[leftChildIndex].compareTo(this.data[rightChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            //比较changeIndex所在的优先级是否比curIndex所在结点的优先级高,如果高,就交换
            if (this.data[changeIndex].compareTo(this.data[curIndex]) > 0) {
                swap(changeIndex, curIndex);
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
    }

    //从堆中取出优先级最高的的元素
    public T removeMaxValueFromHeap() {
        if (isEmpty()) {
            return null;
        }
        // 1、先取出根元素
        T result = this.data[1];

        //2、让树中最后一个元素替换根节点
        this.data[1] = this.data[this.size];
        //更新size
        this.size--;
        siftDown();
        return result;
    }

    //采取交换的方式
    public T removeMaxValueFromHeap2() {
        if (isEmpty()) {
            return null;
        }
        // 1、先取出根元素
        T result = this.data[1];
        //2、获取树中最后一个结点
        T val = this.data[this.size];//(比较结点)
        //更新size
        this.size--;
        //下沉操作 <1>记录下沉结点的索引
        int curIndex = 1;
        //<2>获取下沉结点左右孩子的索引
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex; // 用来保存左右孩子中优先级较高的结点索引
        //判断左孩子是否存在
        while (leftChildIndex <= this.size) { //此时存在左孩子
            int rightChildIndex = leftChildIndex + 1;
            // 判断右孩子是否存在 并且 右孩子结点的优先级大于左孩子结点的优先级
            if (rightChildIndex <= this.size && this.data[leftChildIndex].compareTo(this.data[rightChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            //比较changeIndex所在的优先级是否比curIndex所在结点的优先级高,如果高,就交换
            if (this.data[changeIndex].compareTo(val) > 0) {
                this.data[curIndex] = this.data[changeIndex];
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
        this.data[curIndex] = val;
        return result;
    }

    //元素替换操作 replace 将对顶元素替换成新元素
    public T replace(T newVal) {
        if (isEmpty()) {
            return null;
        }
        T result = this.data[1];
        this.data[1] = newVal;
        //进行下沉操作
        siftDown();

        return result;
    }

    //heapify操作 构建堆操作
    private void heapify() {
        if (this.isEmpty()) {
            return;
        }
        // 将完全二叉树整理成堆
        //1、找到最后一个元素的父亲结点
        int parentIndex = getParentIndex(this.size);

        //2、从最后一个元素的父亲结点开始进行下沉操作,直到根节点
        for (; parentIndex >= 1; parentIndex--) {
            siftDown(parentIndex);
        }
    }

    // 从目标位置进行下沉操作
    private void siftDown(int curIndex) {
        int leftChildIndex = getLeftChildIndex(curIndex);
        int changeIndex = leftChildIndex;
        while (leftChildIndex <= this.size) {
            int rightChildIndex = leftChildIndex + 1;
            if (rightChildIndex <= this.size && this.data[rightChildIndex].compareTo(this.data[leftChildIndex]) < 0) {
                changeIndex = rightChildIndex;
            }
            if (this.data[changeIndex].compareTo(this.data[curIndex]) < 0) {
                //交换操作
                swap(curIndex, changeIndex);
                curIndex = changeIndex;
                leftChildIndex = getLeftChildIndex(curIndex);
                changeIndex = leftChildIndex;
            } else {
                break;
            }
        }
    }

    //交换
    private void swap(int curIndex, int changeIndex) {
        T temp = this.data[curIndex];
        this.data[curIndex] = this.data[changeIndex];
        this.data[changeIndex] = temp;
    }

    //获取最大元素
    public T getMaxValueFromHeap() {
        if (isEmpty()) {
            return null;
        }
        return this.data[1];
    }

    public static void main(String[] args) {
//        Random random = new Random();
//        MaximumHeap<Integer> maximumHeap = new MaximumHeap<>();
//        for (int i = 0; i < 10; i++) {
//            maximumHeap.add(random.nextInt(100));
//        }
//        System.out.println(maximumHeap);
//        int max = maximumHeap.removeMaxValueFromHeap2();
//        System.out.println(max);
//        System.out.println(maximumHeap);
        Integer [] arr = {23, 4, 7, 9, 12, 27, 19, 10, 54, 67, 45};
        System.out.println(Arrays.toString(arr));
        MaximumHeap<Integer> maximumHeap = new MaximumHeap<>(arr);
        System.out.println(maximumHeap);
    }
}

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

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

相关文章

1.1.1半导体基础知识

半导体基础知识 上学的时候大概是模电第一节就会讲完这&#xff0c;会详细的介绍什么是导体&#xff0c;绝缘体&#xff0c;半导体&#xff0c;本征半导体&#xff0c;然后接着详细的讲述本征半导体的结构&#xff0c;两种载流子&#xff0c;P,N杂质半导体&#xff0c;PN结以及…

Spring Boot自动配置--如何切换内置Web服务器

系列文章目录 Spring Boot[概述、功能、快速入门]_心态还需努力呀的博客-CSDN博客 Spring Boot读取配置文件内容的三种方式_心态还需努力呀的博客-CSDN博客 Spring Boot整合Junit_心态还需努力呀的博客-CSDN博客 该系列文章持续更新中~ 目录 系列文章目录 前言 一、默认…

【Python】python深拷贝和浅拷贝(二)

【Python】python深拷贝和浅拷贝&#xff08;二&#xff09; 前言 上一期我们介绍了Python中深拷贝和浅拷贝的定义以及它们在执行过程中内存结构&#xff0c;同时也给出了深拷贝和浅拷贝的方法。&#xff08;没有看上一期的朋友看这里&#xff0c;python深拷贝和浅拷贝&#…

架构设计---性能设计的详解

前言&#xff1a; 系统性能是互联网应用最核心的非功能性架构目标&#xff0c;系统因为高并发访问引起的首要问题就是性能的问题&#xff0c;高并发访问的情况下&#xff0c;系统因为资源不足&#xff0c;处理每个请求的时间都会变慢&#xff0c;看起来就是性能的变差。 因此…

什么是随机森林?

什么是随机森林&#xff1f; 随机森林是一种有监督的机器学习算法。由于其准确性&#xff0c;简单性和灵活性&#xff0c;它已成为最常用的一种算法。事实上&#xff0c;它可以用于分类和回归任务&#xff0c;再加上其非线性特性&#xff0c;使其能够高度适应各种数据和情况。 …

JavaSE初阶篇:系统环境变量path|classpath|JAVA_HOME

环境变量是为了 “在命令行窗口下”编译和运行Java程序而配置一、系统变量&#xff1a;Pathpath环境变量作用&#xff1a;将命令所在的路径配置到path中去&#xff0c;就相当于在计算机中“注册”了一样&#xff0c;以后找这个命令&#xff0c;会直接去你配置的路径下寻找。达到…

Revit中怎么使两面墙拉近时不自动连接?柱断梁墙功能

一、Revit中怎么使两面墙拉近时不自动连接? 问题&#xff1a;同材质的墙体在同一条直线上只要将其端点拖拽至一起就会融合成一道整墙体(如图-1、2所示)&#xff0c;但是在做一些较特殊的结构时我们不需要其连接成一道整墙&#xff0c;有没有什么办法能够设置呢? 我们可以通过…

避免Mysql插入重复数据的几种方法

1、前言 在平时对数据库操作时&#xff0c;有时候需要向数据库中插入一些数据&#xff0c;此时就需要使用数据库的插入语句&#xff0c;但是在向数据中库插入数据时&#xff0c;不能盲目插入&#xff0c;因为盲目的插入可能会造成数据重复&#xff0c;浪费数据库的资源&#x…

Pytest三种配置文件

&#x1f60e;&#x1f60e;原文出自&#xff1a;测个der&#xff0c;博主的公众号&#xff0c;格式美观一些。 配置介绍 pytest 的主配置文件&#xff0c;可以改变 pytest 的默认行为&#xff0c;执行 pytest -h&#xff0c;这里有很多配置均可用于 pytest.ini配置 (venv) …

HarmonyOS鸿蒙学习笔记(16)Canvas入门使用

Canvas入门指南1、Canvas代码结构2、简单案例2.1 fillStyle和fillRect 绘制蓝色矩形2.2 strokeRect和strokeStyle 绘制红色边框2.3 lineCap的使用2.4 lineJoin的使用2.5 closePath的使用3、参考资料1、Canvas代码结构 在使用Canvas的时候&#xff0c;需要向初始化RenderingCon…

第四十七讲:IPv6基础知识

一、IPv6网络元素及概念 IPv6的网络元素如下图所示。 节点&#xff1a;任何运行IPv6的设备&#xff0c;包括路由器和主机&#xff08;甚至还将包括PDA、冰箱、电视等&#xff09;。邻节点&#xff1a;连接到同一链路上的节点。这是一个非常重要的概念&#xff0c;因为IPv6的邻…

JS面试题--this指向+面试题

this其他补充 内置函数的绑定思考 01_一些函数的this分析 // 1.setTimeout原理 // function hySetTimeout(fn, duration) { // fn.call(window) // }// hySetTimeout(function() { // console.log(this) // window // }, 3000)// setTimeout(function() { // console.l…

EEG论文阅读和分析:《Differential entropy feature for EEG-based emotion classification》

论文阅读《Differential entropy feature for EEG-based emotion classification》 论文的核心是提出差分熵作为特征&#xff0c;并且对差分差分熵和比例差分熵等特征进行对比研究。 算法流程步骤&#xff1a; 采样过程&#xff1a; A.预处理 根据受试者的压力反应&#xf…

零基础转行软件测试可行吗?

如今&#xff0c;随着人们对软件质量的要求越来越高&#xff0c;软件测试已经成为最热门的IT行业之一。不少非科班出身的转业者也纷纷开始观望这个行业&#xff0c;因此不少人会问&#xff0c;零基础转行软件测试有前途吗&#xff1f;答案显而易见&#xff0c;如果你想成为最炙…

【C语言】数据类型(基本类型、构造类型、类型转换)

⏰打卡&#xff1a;DAY1 &#x1f3c6;今日学习目标&#xff1a;数据类型&#xff08;基本类型、构造类型、类型转换&#xff09; ✨个人主页&#xff1a;颜颜yan_的个人主页 &#x1f389;专栏系列&#xff1a;从0开始学C语言 文章目录前言基本类型整型浮点型字符型构造类型数…

Mybatis学习——增删改(mysql8.0)

目录 一、配置文件 (一)添加mybatis依赖&#xff1a; (二)resources资源目录的配置 1.database.properties文件的设置 2.mybatis-config.xml文件的设置 二、引入原始类 三、DogDao接口&#xff0c;定义增删改功能 (一)单条数据增删改 1.新增 2.删除 3.修改 (二)多条…

Makefile详细教程

Makefile详细教程 我编写本指南是因为我永远无法完全理解 Makefile。 他们似乎充斥着隐藏的规则和深奥的符号&#xff0c;提出简单的问题并没有得到简单的答案。 为了解决这个问题&#xff0c;我花了几个周末的时间坐下来阅读所有关于 Makefile 的内容。 我已将最关键的知识浓…

【链表】leetcode206.反转链表(C/C++/Java/Js)

leetcode206.反转链表1 题目2 思路2.1 双指针法&#xff08;迭代&#xff09;2.2 递归法2.2.1 递归--从前往后翻转指针指向2.2.2 递归--从后往前翻转指针指向3 代码3.1 C版本&#xff08;迭代&#xff09;3.2 C版本&#xff08;迭代递归&#xff09;3.3 Java版本&#xff08;迭…

【OpenDDS开发指南V3.20】第十章:Java Bindings

介绍 OpenDDS提供JNI绑定。Java应用程序可以像C++应用程序一样使用完整的OpenDDS中间件。 有关入门的信息,包括先决条件和依赖项,请参阅$DDS_ROOT/java/INSTALL文件。 Java版本9和更高版本使用Java平台模块系统。要在这些Java版本中使用OpenDDS,请将MPC特性Java_pre_jpms…

QT Echarts 联动共享数据表图 使用详解

Echarts是百度的一款可视化界面开发的平台&#xff0c;里面的地图以及数据可视化内容十分丰富&#xff0c;适合用于一些大屏数据显示项目或者一些ui界面开发。每一个ECharts图表使用一个无边框的QWebView来展示&#xff0c;这样多个不同类型的ECharts图表就是多个封装不同类型E…