Java 数据结构篇-实现堆的核心方法与堆的应用(实现 TOP-K 问题:最小 k 个数)

🔥博客主页: 【小扳_-CSDN博客】
❤感谢大家点赞👍收藏⭐评论✍
  

 

文章目录

        1.0 堆的说明

        2.0 堆的成员变量及其构造方法 

        3.0 实现堆的核心方法

        3.1 实现堆的核心方法 - 获取堆顶元素 peek()

        3.2 实现堆的核心方法 - 下潜 down(int i)

        3.3 实现堆的核心方法 - 交换元素 swap(int i,int j)

        3.4 实现堆核心方法 - 删除堆顶元素 poll()

        3.5 实现堆的核心方法 - 替换堆顶元素 replace(int i)

        3.6 实现堆的核心方法 - 添加元素 offer(int value)

        3.7 实现堆的核心方法 - 建堆 heapify()

        3.8 实现堆的核心方法完整代码

        4.0 TOP - K 问题:最小的 K 个数

        4.1 实现最小 k 个数的思路

        4.2 代码实现最小 k 个数


        1.0 堆的说明

        堆(Heap)是一种基于树的数据结构,通常用于动态分配内存空间。堆可以被看作是一棵完全二叉树,其中每个节点都满足堆的性质,即父节点的值大于或等于子节点的值(大根堆),或父节点的值小于或等于子节点的值(小根堆)。在堆中,根节点的值是最大或最小的,因此也被称为最大堆或最小堆。

        堆的实现通常使用数组来存储堆中的元素通过计算数组下标来实现节点之间的关系。堆的时间复杂度为 O(log n),其中 n 是堆中元素的数量。

        堆的操作包括插入删除查找等。插入操作将一个新元素插入到堆中,删除操作将堆中的最大或最小元素删除,查找操作可以在堆中查找特定元素的位置。

        2.0 堆的成员变量及其构造方法 

        主要的成员变量为:int[] arr 数组:用来存放元素的容器;int size :代表当前的元素个数。

        构造方法:指定数组大小带参数的构造器参数为数组的构造器

代码如下:

public class MyHeap {
    public int[] arr;
    public int size;

    public MyHeap(int capacity) {
        arr = new int[capacity];
    }

    public MyHeap(int[] arr) {
        this.arr = arr;
        this.size = arr.length;
        heapify();
    }

}

        

        3.0 实现堆的核心方法

        获取堆顶元素、下潜、交换元素、添加元素、替换元素、删除元素、建堆。

        3.1 实现堆的核心方法 - 获取堆顶元素 peek()

        用数组实现堆,在获取堆顶元之前,先需要判断该数组是否为空,若不为空,则直接返回数组索引为 0 的元素;若数组为空,则返回 0 或者抛出异常也可以。

代码如下:

    //获取栈顶元素
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return arr[0];
    }

        3.2 实现堆的核心方法 - 下潜 down(int i)

        该方法主要用于删除栈顶元素、替换元素等核心方法。下潜的意思就是将当前的元素所在的位置不符合大顶堆或者小顶堆的规则,因此需要向下调整。找到合适的位置来存放当前的元素

 具体下潜的思路:

假设需要满足大顶堆的规则:

        由以上的图来看,当前的索引为 0 处的元素 7 小于该左孩子的元素,因此当前不满足大顶堆的规则。需要将两者进行交换。

交换的结果为:

        交换完之后,当前索引为 2 处的元素 7 小于该右孩子的元素,所以索引 2 与 索引 5 需要继续交换。若当前为 i 该右孩子的索引 left = 2 * i + 1;该左孩子的索引 right = 2 * i + 2 (也可以表示为 right = left + 1 )一开始默认当前的最大元素的索引 max = i ,接着来判断该左右孩子的元素是否大于当前索引 max ,若大于当前索引 max 时,需要进行交换 max = left 或者 max = right 。若不大于当前索引为 max 处的元素,则不需要交换。由于每一次都是子问题过程,所以可以利用递归来实现,当且仅当 max != i 时,说明 max 已经被交换过了,需要继续向下递出,直到 max == i 时,结束递出,开始回溯。当然,这里不需要回带任何值或者变量,即该递归函数的返回类型为 viod 。

代码如下:

    //下潜
    public void down(int i) {
        int left = 2 * i + 1;
        int right = left + 1;
        int max = i;
        if (left < size && arr[left] > arr[max]) {
            max = left;
        }
        if (right < size && arr[right] > arr[max]) {
            max = right;
        }
        if (max != i) {

            //交换
            swap(i,max);

            //继续下潜
            down(max);
        }
    }

        3.3 实现堆的核心方法 - 交换元素 swap(int i,int j)

        交换数组索引中 i 与 j 处的元素

代码如下:

    //交换
    public void swap(int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

        3.4 实现堆核心方法 - 删除堆顶元素 poll()

        具体实现思路:为了更高效率的删除堆顶元素后保持原来大顶堆或者小顶堆的规则。

        步骤一:先将堆顶元素与最后一个元素进行交换。即 arr[0] = arr[size - 1] 。

        步骤二:将 size-- 。

        步骤三:交换后的堆,可能会不满足大顶堆或者小顶堆的规则,则需要将堆顶元素进行下潜调整,找到合适的位置存放该元素。最后需要返回删除的元素。

代码如下:

    //删除堆顶元素
    public int poll() {
        if (isEmpty()) {
            return 0;
        }
        int t = arr[0];
        arr[0] = arr[size - 1];
        size--;
        //下潜
        down(0);

        return t;
    }

        注意:在删除堆顶元素之前,需要先判断当前的数组是否为空数组。

        同理,若需要删除指定堆中的元素索引,实现思路是一样的。

代码如下:

    //指定删除元素
    public int poll(int i) {
        if (i > size) {
            return 0;
        }
        int temp = arr[i];
        arr[i] = arr[size - 1];
        size--;
        //下潜
        down(i);
        return temp;
    }

        先判断索引是否合法,若不合法,则返回 0 或者抛出异常也可以。

        3.5 实现堆的核心方法 - 替换堆顶元素 replace(int i)

        具体思路为:先判断该数组是否为空数组,若不为空数组,则直接替换堆顶元素 arr[0] = i;之后可能会不满足大顶堆或者小顶堆的规则,所以索引为 0 处需要下潜调整,找到合适的位置存放元素。

代码实现:

    //替换堆顶元素
    public void replace(int i) {
        if (isEmpty()) {
            return;
        }
        arr[0] = i;
        down(0);
    }

        3.6 实现堆的核心方法 - 添加元素 offer(int value)

        具体实现思路:先判断当前索引为 i = size 处的双亲节点为 j = (i - 1) / 2 ,判断 arr[j] 与 value 的大小,若为大顶堆规则,则当 arr[j] > value 时,不需要继续往上走了,在 i 处存放 value 即可 arr[i] = value ;当 arr[j] <= value 时,先将 arr[j] 处的元素存放在 arr[i] 中,接着需要继续往上走, i = j ,j = (i - 1) / 2 直到 i == 0 或者 arr[j] > value 时,退出循环。在 arr[i] 处存放 value

代码如下:

    //添加元素
    public boolean offer(int value) {
        if (isFull()) {
            return false;
        }
        int i = size;
        int j = (size - 1) / 2;
        while (i > 0 && arr[j] < value) {
            arr[i] = arr[j];
            i = j;
            j = (i - 1) / 2;
        }
        arr[i] = value;
        size++;
        return true;
    }

        需要注意:添加元素前,需要先判断该数组是否满了。还有添加完之后,需要进行 size++

        3.7 实现堆的核心方法 - 建堆 heapify()

        该方法实现的意义,若随机给出一个数组,需要实现由大顶堆或者小顶堆的结构存放元素。因此就会用到该方法。

        实现思路为:需要找到最后一个非叶子节点,从后往前,将当前的元素进行下潜处理即可完成建堆。

代码如下:

    //建堆
    public void heapify() {

        //先找到最后非叶子节点
        int lastNonLeafNodes = size / 2 - 1;
        for (int i = lastNonLeafNodes; i >= 0 ; i--) {
            //下潜
            down(i);
        }
    }

        3.8 实现堆的核心方法完整代码

public class MyHeap {
    public int[] arr;
    public int size;

    public MyHeap(int capacity) {
        arr = new int[capacity];
    }

    public MyHeap(int[] arr) {
        this.arr = arr;
        this.size = arr.length;
        heapify();
    }

    //获取栈顶元素
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        return arr[0];
    }

    //删除堆顶元素
    public int poll() {
        if (isEmpty()) {
            return 0;
        }
        int t = arr[0];
        arr[0] = arr[size - 1];
        size--;
        //下潜
        down(0);

        return t;
    }

    //指定删除元素
    public int poll(int i) {
        if (i > size) {
            return 0;
        }
        int temp = arr[i];
        arr[i] = arr[size - 1];
        size--;
        //下潜
        down(i);
        return temp;
    }

    //替换堆顶元素
    public void replace(int i) {
        if (isEmpty()) {
            return;
        }
        arr[0] = i;
        down(0);
    }


    //添加元素
    public boolean offer(int value) {
        if (isFull()) {
            return false;
        }
        int i = size;
        int j = (size - 1) / 2;
        while (i > 0 && arr[j] < value) {
            arr[i] = arr[j];
            i = j;
            j = (i - 1) / 2;
        }
        arr[i] = value;
        size++;
        return true;
    }

    //建堆
    public void heapify() {

        //先找到最后非叶子节点
        int lastNonLeafNodes = size / 2 - 1;
        for (int i = lastNonLeafNodes; i >= 0 ; i--) {
            //下潜
            down(i);
        }
    }

    //下潜
    public void down(int i) {
        int left = 2 * i + 1;
        int right = left + 1;
        int max = i;
        if (left < size && arr[left] > arr[max]) {
            max = left;
        }
        if (right < size && arr[right] > arr[max]) {
            max = right;
        }
        if (max != i) {

            //交换
            swap(i,max);

            //继续下潜
            down(max);
        }
    }

    //交换
    public void swap(int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }


    //判断是否为空数组
    public boolean isEmpty() {
        return size == 0;
    }

    //判断是否为满数组
    public boolean isFull() {
        return  size == arr.length;
    }
}

        4.0 TOP - K 问题:最小的 K 个数

题目:

        设计一个算法,找出数组中最小的k个数。以任意顺序返回这 k 个数均可。

示例:

输入: arr = [1,3,5,7,2,4,6,8], k = 4
输出: [1,2,3,4]

提示:

0 <= len(arr) <= 100000

0 <= k <= min(100000, len(arr))

OJ 链接:

面试题 17.14. 最小K个数

        4.1 实现最小 k 个数的思路

        具体思路为:结合大顶堆的数据结构的特点,根节点的元素永远比孩子节点的元素大。先将给定的 arr 数组的前 k 个元素直接通过 heap.offer() 方法添加到大顶堆上,然后 arr 数组剩下的元素需要跟堆顶元素相对比,若堆顶元素大于 arr[i] 中的元素,则需要进行交换,将 arr[i] 的元素替换到堆顶,接着还不能结束,有可能替换完的元素就不符合大顶堆的规则了,因此还需要将堆顶元素下潜处理调整,找到合适的位置存放该元素;若堆顶元素不大于 arr[i] 中的元素,则不需要交换。一直将 arr 数组中的元素遍历结束,则循环停止。最后堆上存储的 k 个元素就是该数组 arr 中最小的元素了。

        4.2 代码实现最小 k 个数

public class Solution {
    public int[] smallestK(int[] arr, int k) {
        MaxHeap heap = new MaxHeap(k);
        for(int i = 0; i < k ; i++) {
            heap.offer(arr[i]);
        }
        for(int i = k; i < arr.length; i++) {
            if(heap.peek() > arr[i]) {
                heap.arr[0] = arr[i];
                heap.down(0);
            }
        }
        return heap.arr;
    }

}

//实现一个大顶堆
class MaxHeap {
    int[] arr;
    int size;

    public MaxHeap(int capacity) {
        arr = new int[capacity];
    }

    public MaxHeap(int[] smallestK) {
        this.arr = smallestK;
        this.size = smallestK.length;
    }

    //插入元素
    public boolean offer(int value) {
        if(isFull()) {
            return false;
        }
        int i = size;
        int j = (i - 1) / 2;
        while(i > 0 && arr[j] < value) {
            arr[i] = arr[j];
            i = j;
            j = (i - 1) / 2;
        }
        arr[i] = value;
        size++;
        return true;
    }

    //删除堆顶元素
    public int poll() {
        if(isEmpty()) {
            return 0;
        }
        int ret = arr[0];
        arr[0] = arr[size - 1];
        size--;
        down(0);
        return ret;
    }
    //下潜
    public void down(int i) {
        int left = 2 * i + 1;
        int right = left + 1;
        int max = i;
        if(left < size && arr[left] > arr[max]) {
            max = left;
        }
        if(right < size && arr[right] > arr[max]) {
            max = right;
        }
        if(max != i) {
            swap(max,i);
            down(max);
        }

    }

    //交换
    public void swap(int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    //获取堆顶元素
    public int peek() {
        if(isEmpty()) {
            return 0;
        }
        return arr[0];
    }

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

    //判断是否为满
    public boolean isFull() {
        return size == arr.length;
    }

}

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

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

相关文章

关键点检测_labelme标注的json,随机裁剪(添加偏移相当于数据增强)

import json import os import numpy as np import cv2 import glob import csv import random#通过表格获取csv # def csv_tws(root, name): # csv_path = root+"/csv/{}.png.csv".format(name)

Java并发(二十)----synchronized原理进阶

1、小故事 故事角色 老王 - JVM 小南 - 线程 小女 - 线程 房间 - 对象 房间门上 - 防盗锁 - Monitor-重量级锁 房间门上 - 小南书包 - 轻量级锁 房间门上 - 刻上小南大名 - 偏向锁 -对象专属于某个线程使用 批量重刻名 - 一个类的偏向锁撤销到达 20 阈值 -批量重偏向 …

linux之Samba服务器

环境&#xff1a;虚拟机CENTOS 7和 测试机相通 一、Samba服务器_光盘共享&#xff08;匿名访问&#xff09; 1.在虚拟机CENTOS 7安装smb服务&#xff0c;并在防火墙上允许samba流量通过 2. 挂载光盘 3.修改smb.conf配置文件&#xff0c;实现光盘匿名共享 4. 启动smb服务 5.在…

Postman使用总结--生成测试报告

1.执行生成的命令格式 newman run 用例集文件 .json -e 环境文件 .json -d 数据文件 .json/.csv -r htmlextra --reporter- htmlextra-export 测试报告名 .html -e 和 -d 是 非必须的。 如果没有使用 环境&#xff0c;不需要指定 -e 如果没有使用 数据…

二叉树【数据结构】

目录 二叉树1. 二叉树定义二叉树的存储定义 2. 遍历二叉树(1) 前序遍历(2) 中序遍历(3) 后序遍历(4) 层序遍历 3. 二叉树的相关操作(1) 二叉树的初始化(2) 二叉树的结点的手动创建(3) 二叉树结点的个数(4) 二叉树叶子结点的个数(5) 二叉树的高度(6) 第k层结点个数(7) 通过前序遍…

机场信息集成系统系列介绍(4):机场信息集成总线系统

机场信息集成总线系统是一种专为机场运营管理设计的先进系统&#xff0c;旨在提高机场的航班调度指挥效率&#xff0c;同时为机场各生产部门提供航班保障计划的制定和实时调整功能。该系统的核心用户是机场运控部门。 机场信息集成总线系统&#xff08;Airport Information In…

Qt-QTransform介绍与使用

QTransform是一个用于二维坐标系转换的类。我们知道Qt的坐标系是左上角为原点&#xff0c;x轴向右&#xff0c;y轴向下&#xff0c;屏幕上每个像素代表一个单位&#xff0c;那么&#xff0c;如果我们想要在屏幕上建立自己的坐标系用于绘制&#xff0c;就需要借助QTransform。 …

什么品牌的猫罐头好吃?五大性价比高的猫罐头测评

不知不觉已经养猫两年啦&#xff0c;大大小小也算是尝试过很多猫罐头了。一开始我也是踩了很多坑&#xff0c;各种踩雷。我深知猫罐头的各种门道&#xff0c;新手一不小心就会着道了。 作为一个经营猫咖5年的老板&#xff0c;大促期间我总能捡漏&#xff0c;屯到一大波好吃又放…

一、Java基础语法

注意&#xff1a; ​ 用记事本打开本文档&#xff0c;格式较差。 ​ 可安装typora软件后再次打开。 ​ 安装包位于&#xff1a;day01\资料\其他软件\阅读笔记的软件\typora-setup-x64.exe day01 - Java基础语法 1. 人机交互 1.1 什么是cmd&#xff1f; 就是在windows操作…

JS的浅拷贝和深拷贝

首先理解什么是浅拷贝和深拷贝&#xff1a; 浅拷贝&#xff1a; 浅拷贝只会复制对象的第一层属性&#xff0c;而不会递归地复制嵌套的对象。浅拷贝仅复制对象的引用&#xff0c;新对象和原始对象仍然共享相同的引用&#xff0c;因此对新对象的修改可能会影响到原始对象。浅拷…

Postman/Apifox使用教程

Postman/Apifox使用教程 1. 界面导航说明2.发送第一个请求3. 工具的基础功能3.1 常见类型的接口请求3.1.1 查询参数的接口请求3.1.2 表单类型的接口请求3.1.3 上传文件的表单请求3.1.4 json类型的接口请求 3.2 接口响应数据解析 附录 1. 界面导航说明 2.发送第一个请求 http:/…

css的filter全属性介绍

原图&#xff1a; 模糊&#xff08;blur&#xff09; 单位可为px或rem&#xff0c;值越大&#xff0c;越模糊 filter:blur(3px) filter:blur(0.3rem) 亮度(brightness) 值可为数字或百分数&#xff0c;小于1时&#xff0c;亮度更暗&#xff1b;等于1时&#xff0c;无变化&am…

非递归实现的快速排序

目录 序列文章 前言 学前补充 非递归快速排序 注意事项&#xff08;重要&#xff09; 实现步骤 代码实现 时空复杂度 快速排序的特性 栈的相关代码 序列文章 非递归实现的快速排序&#xff1a;http://t.csdnimg.cn/UEcL6 快速排序的挖坑法与双指针法&#xff1a;ht…

如何免费搭建私人电影网站(二)

前一篇的准备工作做好后就进行下面的具体操作 1、免费主机申请步骤 产品——立即开通 开通成功后会出现IP地址和网站地址如下图 点击进去管理 设置FTP密码和MYSQL数据库密码 设置好后&#xff0c;就可以通过FTP文件上传工具&#xff0c;将下载好的网站模版上传到空间了 FTP…

178. 第K短路(A*启发式算法)

178. 第K短路 - AcWing题库 给定一张 N 个点&#xff08;编号 1,2…N&#xff09;&#xff0c;M 条边的有向图&#xff0c;求从起点 S 到终点 T 的第 K 短路的长度&#xff0c;路径允许重复经过点或边。 注意&#xff1a; 每条最短路中至少要包含一条边。 输入格式 第一行包…

python self用法详解

对于在类体中定义的实例方法&#xff0c;Python 会自动绑定方法的第一个参数&#xff08;通常建议将该参数命名为 self&#xff09;&#xff0c;第一个参数总是指向调用该方法的对象。根据第一个参数出现位置的不同&#xff0c;第一个参数所绑定的对象略有区别&#xff1a; 在…

vue npm install 报错处理

一。Error: Cant find Python executable "python", you can set the PYTHON env variable 解决办法&#xff1a; 1、安装windows-build-tools npm install --global --production windows-build-tools2.安装node-gyp npm install --global node-gyp

云渲染农场如何付费使用?动画、效果图都一般怎么收费得?

许多刚入门计算机图形学的朋友可能会疑问&#xff0c;为何在渲染过程中需要借助云渲染服务&#xff0c;以及为何这些服务不是免费的。简单来讲&#xff0c;就是因为渲染工作量庞大&#xff0c;本机处理能力有限。设计和动画行业日渐将云渲染视为基本开支&#xff0c;这是因为数…

linux机器下/etc/hosts和/etc/resolv.conf文件解析

文章目录 1. /etc/resolv.conf1.1 概念1.2 配置1.3 用途 2. /etc/hosts2.1 概念2.2 配置2.3 两者优先级对比 1. /etc/resolv.conf 1.1 概念 DNS客户机的配置文件&#xff0c;用于设置DNS服务器的IP地址及DNS域名&#xff0c;还包含了主机的域名搜索顺序。该文件是由域名解析器…

5. Prism系列之区域管理器

Prism系列之区域管理器 文章目录 Prism系列之区域管理器一、区域管理器二、区域创建与视图的注入1. ViewDiscovery2. ViewInjection 三、激活与失效视图1. Activate和Deactivate2. 监控视图激活状态3. Add和Remove 四、自定义区域适配器1. 创建自定义适配器2. 注册映射3. 创建区…