数据结构 --- 堆

news2025/2/25 23:47:17

1、堆的基本概念

之前在学习优先级队列的时候, 学习到了堆的概念,现在重新回忆一下:

  • 堆在逻辑上,是一颗完全二叉树
  • 堆在物理上,是存储在数组中的
  • 任意根节点值>=子树节点值,叫做大顶堆。
  • 任意根节点值<=子树节点值,叫做小顶堆。
  • 堆的基本作用是快速找到集合中的最值。

在这里插入图片描述

 

前置知识点:

从索引0开始存储节点数据有如下规律:

  1. 节点i的父节点索引为 floor((i-1)/2) ,i>0
  2. 节点i的左子节点为 2i+1,右子节点为 2i+2,当然索引都要小于堆容量size

2、堆的实现

1、节点的下沉操作

  1. 先找出当前节点的左右子节点
  2. 定义一个max值,代表节点数据最大的值的索引,赋初始值为parent节点
  3. 和左右节点分别比较(如果有子节点),更新max值
  4. 如果max值有改变,说明子节点比当前节点打,执行下沉操作
  5. 递归调动下沉操作

    // 下沉操作
    private void down(int parent) {
        int left = parent * 2 + 1;
        int right = left + 1;
        int max = parent;
        if (left < size && array[max] < array[left]) {
            max = left;
        }
        if (right < size && array[max] < array[right]) {
            max = right;
        }
        if (parent != max) {
            swap(parent, max);
            // 这里有个递归调用,跳出递归的条件就是parent == max
            down(max);
        }
    }

    // 交换操作
    private void swap(int x, int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }

 2、节点的上浮操作

  1. 计算出父节点的坐标index
  2. 当前节点和父节点进行比较,如果比父节点大,那么就执行上浮操作
  3. 循环1和2两个步骤,直到跳出循环。
    private void up(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0 && array[parent] < array[child]) {
            swap(parent, child);
            child = parent;
            parent = (child - 1) / 2;
        }
    }

3、堆的构造方法 --- 建立堆的操作

 找到堆中的最后一个非叶子节点,从当前节点往前遍历,不断的执行下沉操作

    public void heapify() {
        // 先找到最后一个非叶子节点(size/2-1),往前遍历,对每个节点执行下潜操作。
        for (int i = size / 2 - 1; i >= 0; i--) {
            down(i);
        }
    }

4、取出堆顶元素

  1. 堆顶元素先用一个临时变量存储,便于返回
  2. 将堆顶元素和最后一个元素互换位置,这个时候,size-- 就相当于把堆顶元素移除了
  3. 此时新的堆顶元素不符合大顶堆的规律,执行下沉操作即可

移除指定元素的操作,思路也是一样的,代码如下

    // 移除堆顶部元素方法
    public int poll() {
        return poll(0);
    }

    // 移除指定位置元素方法
    public int poll(int index) {
        if (isEmpty() || index >= size || index < 0) {
            return 0;
        }
        int removed = array[index];
        swap(index, size - 1);
        size--;

        down(index);

        return removed;
    }

5、添加新的元素

  1. 将新元素添加到最后的位置
  2. 对最后的位置进行上浮操作,然后size++;

如果数组满了,可以执行扩容,或者其他操作

    public boolean offer(int offered) {
        if (isFull()) {
            array = Arrays.copyOf(array, array.length*2);
        }
        array[size] = offered;
        up(size);
        size++;
        return true;
    }

3、利用堆排序

算法描述:

  1. heapify 建立大顶堆
  2. 堆顶和堆底的元素交换顺序,缩小size并下沉调整堆
  3. 重复第二步,直到堆中只剩一个元素

 

其他排序方法:

前面已经实现了大顶堆,其实poll方法拿出来的,就是当前最大的元素,因此排序方法可以写成下面的格式。

    // 倒叙排序
    public int[] sort(int[] arr) {
        MaxHeap maxHeap = new MaxHeap(arr);
        int[] res = new int[maxHeap.size];
        int count = 0;
        while (maxHeap.size > 0) {
            res[count] = maxHeap.poll();
            count++;
        }
        return res;
    }

    // 正序排序
    public int[] sort2(int[] arr) {
        MaxHeap maxHeap = new MaxHeap(arr);
        int[] res = new int[maxHeap.size];
        int count = maxHeap.size - 1;
        while (maxHeap.size > 0) {
            res[count] = maxHeap.poll();
            count--;
        }
        return res;
    }

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

解题思路:

可以利用堆排序来求解:

  1. 使用小顶堆,先把数组前K个元素入队,这样堆顶的元素就是前K个元素中,第K个大的
  2. 从k+1个元素开始,与堆顶元素进行比较,如果比堆顶元素大,那么就替换堆顶元素,然后重新构建小顶堆
  3. 循环2步骤,循环完成,堆顶的元素就是所求的值。
    // 借用PriorityQueue实现小顶堆
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int i = 0; i < k; i++) {
            queue.offer(nums[i]);
        }
        for (int i = k; i < nums.length; i++) {
            if (nums[i] > queue.peek()) {
                // 这里replace方法更好,但是因为PriorityQueue没有这个方法,
                // 所以先出后入,效果一样,但是效率会降低
                queue.poll();
                queue.offer(nums[i]);
            }
        }
        return queue.peek();
    }

703. 数据流中的第 K 大元素

题目比较难以理解,简单的说就是,给一个数组,和一个k的值,进行初始化,每次add的时候,加入一个元素,然后,把当前数据里面第K大的元素删除并返回。

解题思路:

  1. 定义一个小顶堆,可以用优先队列,存储前K大的元素,那么栈顶,就是第K大的元素。
  2. 加入元素到队列中,如果size大于k,那么就poll,保证队列中只有K个元素
  3. 返回堆顶的元素,就是本题的解。
class KthLargest {
    int k;
    PriorityQueue<Integer> queue;

    public KthLargest(int k, int[] nums) {
        this.k = k;
        queue = new PriorityQueue<>();
        for (int num : nums) {
            queue.offer(num);
        }
    }
    
    public int add(int val) {
        queue.offer(val);
        while (queue.size() > k) {
            queue.poll();
        }
        return queue.peek();
    }
}

295. 数据流的中位数

不断的往定义的列表中添加数据,取这个列表中的中位数

解题思路:

  1. 定义两个优先级队列,一个大顶堆,一个小顶堆
  2. 列表中的元素需要满足以下条件
    1. 两个优先级的队列中,元素之差不能超过1
    2. 大顶堆中的数据都是列表中值比较小的一半
    3. 小顶堆中的数据都是列表中值比较大的一半

实现代码:

  1. 添加元素时,要添加的元素和大顶堆中的堆顶元素对比,如果比他小,则添加到大顶堆中,否则,添加到小顶堆中
  2. 添加完元素,要平衡两个队列的元素数量,如果大顶堆的数据比小顶堆中的数据多2,那么就把大顶堆中堆顶的数据poll出来offer进小顶堆,反之亦然。
  3. 取中位数操作:如果队列数据是奇数,那么取元素较多的哪个堆的堆顶数据即可,如果为偶数,那么取两个堆顶数据求平均值。
public class MedianFinder {
    int sizeA;

    int sizeB;

    PriorityQueue<Integer> queue1;

    PriorityQueue<Integer> queue2;

    public MedianFinder() {
        this.sizeA = 0;
        this.sizeB = 0;
        // 大顶堆
        queue1 = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        // 小顶堆
        queue2 = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
    }

    public void addNum(int num) {
        if (sizeA == 0) {
            queue1.offer(num);
            sizeA++;
            return;
        }

        if (queue1.peek() > num) {
            queue1.offer(num);
            sizeA++;
            if (sizeA - sizeB > 0) {
                queue2.offer(queue1.poll());
                sizeA--;
                sizeB++;
            }
        } else {
            queue2.offer(num);
            sizeB++;
            if (sizeB - sizeA > 0) {
                queue1.offer(queue2.poll());
                sizeB--;
                sizeA++;
            }
        }
    }

    public double findMedian() {
        double x;
        if (sizeA - sizeB == 1) {
            x = queue1.peek() * 1.0;
        } else if (sizeB - sizeA == 1) {
            x = queue2.peek() * 1.0;
        } else {
            x = (queue1.peek() + queue2.peek()) / 2.0;
        }
        return x;
    }

    public static void main(String[] args) {
        MedianFinder medianFinder = new MedianFinder();
        medianFinder.addNum(40);
        System.out.println(medianFinder.findMedian());
        medianFinder.addNum(12);
        System.out.println(medianFinder.findMedian());
        medianFinder.addNum(16);
        System.out.println(medianFinder.findMedian());
    }
}

本题代码是借用Java中的优先队列实现大顶堆和小顶堆。

提供一种更加简单的写法,但是效率会低一些

class MedianFinder {
    PriorityQueue<Integer> queue1;

    PriorityQueue<Integer> queue2;

    public MedianFinder() {
        // 大顶堆
        queue1 = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });
        // 小顶堆
        queue2 = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
    }

    public void addNum(int num) {
        // 队列元素相等,就把元素加入到q1中,但是要先加q2,然后poll出来在加q1,
        if (queue1.size() == queue2.size()) {
            queue2.offer(num);
            queue1.offer(queue2.poll());
        } 
        // 队列元素不等,就把元素加到q2中,但是要先加入q1,然后poll出来在加q2,
        else {
            queue1.offer(num);
            queue2.offer(queue1.poll());
        }
    }

    public double findMedian() {
        if (queue2.size() == queue1.size()) {
            return (queue2.peek() + queue1.peek()) / 2.0;
        } else {
            return queue1.peek();
        }
    }
}

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

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

相关文章

学习RabbitMQ高级特性

目标&#xff1a; 了解熟悉RabbitMQ的高级特性 学习步骤&#xff1a; 高级特性主要分为以下几点, 官网介绍 1、消息可靠性投递 【confirm 确认模式、return 退回模式】 2、Consumer ACK 【acknowledge】 3、消费端限流 【prefetch】 4、TTL过期时间 【time to live】 5、死信队…

SQL注入 - Part 3(带外)

1、DNSLog注入 &#xff08;一种注入新思路&#xff09; 可以看到DNS的解析日志中包含了用户名。 基于此原理&#xff0c;可以手工构造注入点&#xff0c;让DNSlog显示库名、表名等&#xff0c;也可以使用自动化脚本Dnslogsqlinj进行获取。 2、SQL注入的防御 基于关键字&…

rollup打包react组件

这次主要简单实现用rollup打包react组件&#xff0c;组件的话简单写了一个弹窗组件&#xff0c;效果如下&#xff1a; 点击打开弹框&#xff0c;点击关闭按钮关闭弹框 首先创建react项目&#xff0c;这边还是用mfex-project脚手架创建 mfex-project create react-demo 然后编…

Linux·深入理解IO复用技术之epoll

目录 1.写在前面 2.初识复用技术和IO复用 3. Linux的IO复用工具概览 4. 初识epoll 5. epoll的底层细节 6.LT模式和ET模式 7.epoll的惊群问题 1.写在前面 今天一起来学习一下高并发实现的的重要基础&#xff1a;I/O复用技术 & epoll原理。 通过本文你将了解到以下内容…

【JavaScript】ES6,Proxy,Reflect,Promise,生成器,async/await

❤️ Author&#xff1a; 老九 ☕️ 个人博客&#xff1a;老九的CSDN博客 &#x1f64f; 个人名言&#xff1a;不可控之事 乐观面对 &#x1f60d; 系列专栏&#xff1a; 文章目录 ES6模板字符串&#xff0c;标签模板字符串函数的默认参数函数的剩余参数剩余参数和arguments有什…

为什么要学习C++软件调试技术?掌握调试技术都有哪些好处?

目录 1、为什么要学习C软件调试技术&#xff1f; 1.1、IDE调试手段虽必不可少&#xff0c;但还不够 1.2、通过查看日志和代码去排查异常崩溃问题&#xff0c;费时费力&#xff0c;很难定位问 1.3、有的问题很难复现&#xff0c;可能只在客户的环境才能复现 1.4、开发联调工…

使用git远程上传github

如果你不是很熟悉git&#xff0c;在使用git前请先对你的项目进行备份 进入本地文件目录&#xff0c;打开git的cmd界面&#xff08;Git Bash Here&#xff09;如果当面目录使用过git&#xff0c;有.git隐藏文件&#xff0c;则跳过第二步&#xff0c;没有则输入以下命令 git ini…

零基础如何自学网络安全?

第一阶段&#xff1a;学习一种或几种编程语言。 网络安全也属于计算机范畴&#xff0c;涉及到IT行业的&#xff0c;编程语言是不可难免的。 《Head First Python(第2版)》 作为一种高级编程语言&#xff0c;Python越来越受到网络专家的欢迎。它之所以吸引人&#xff0c;主要…

【Java 28岁了】一个有趣的例子,再推荐一些经典好书(文末惊喜福利)

文章目录 1 写在前面2 C语言与Java语言的互相调用2.1 C语言调用Java语言2.2 Java语言调用C语言 3 友情推荐4 更多分享 1 写在前面 众所周知&#xff0c;C语言和Java语言是两种不同的编程语言&#xff0c;它们的关系可以描述为Java语言是在C语言的基础上发展而来的一种高级编程…

内网如何映射到公网访问互联网

我们通常会根据本地应用场景来选择合适的中间件来搭建服务器。tomcat、 apache是比较常用的搭建服务器的中间件&#xff0c;它们之间还是有一些区别差异的。在内网本地部署搭建服务器后&#xff0c;还可以通过快解析端口映射方法&#xff0c;将内网应用地址发布到互联网&#x…

Springboot +spring security,使用过滤器方式实现验证码功能

一.简介 在前面文章章节通过自定义认证器实现了验证码功能&#xff0c;这篇文章使用过滤器来实现验证码功能。 二.思路分析 实现逻辑和通过过滤器实现json格式登录一样&#xff0c;需要继承UsernamePasswordAuthenticationFilter&#xff0c;所以文档这块主要记录下代码实现…

每日一题——两数之和(返回下标和返回数值两种情况)

每日一题 两数之和 题目链接 思路 注&#xff1a;本题只采用暴力解法&#xff0c;时间复杂度为O(n2)&#xff0c;如果采用哈希表&#xff0c;可以将时间复杂度降到O(n)&#xff0c;但由于笔者还未对哈希表展开学习&#xff0c;故不做讨论 我们直接用两层for循环来解决问题 第…

安装部署 Mastodon 长毛象去中心化微博系统

注意&#xff1a;本文采用的为 Docker Compose 方式安装部署。 首先选择你安装的版本&#xff0c;有以下两种推荐&#xff1a; 官方版本&#xff1a; https://github.com/mastodon/mastodonGlitch 版本&#xff1a; https://github.com/glitch-soc/mastodon 项目包含一个 Doc…

不同的去耦电容 阻抗VS频率

不同的去耦电容 阻抗VS频率 • 并联电容可以在一个较宽的频带内降低阻抗 • 小的去耦电容尽可能靠近电源引脚 为什么每个电源引脚都需要去耦 去耦电容总结 • 旁路电容离电容引脚尽可能的近 • SMT磁珠对于降低Ripple非常有效 • 高频时需要地平面 – 最小化寄生参数 • 使用…

记两道小明文攻击题

题一 题目描述&#xff1a; flag **************************** flag "asfajgfbiagbwe" p getPrime(2048) q getPrime(2048) m1 bytes_to_long(bytes(flag.encode()))e1e2 3087 n p*q print()flag1 pow(m1,e1,n) flag2 pow(m1,e2,n) print(flag1 str(fla…

MATLAB 搜索某一点的R邻域点 (13)

MATLAB 搜索某一点的R邻域点 (13) 前言一、算法介绍1.1 :无序点云的R邻域点搜索1.2 :有序点云的R邻域点搜索二、具体实现示例2.1 算法一 (含详细注释)2.2 算法二 (含详细注释)前言 在点云处理中,最基本的算法之一就是搜索某一点的近邻点,这在重叠区域确定,点云密度…

Spring Gateway使用JWT实现统一身份认证

在开发集群式或分布式服务时&#xff0c;鉴权是最重要的一步&#xff0c;为了方便对请求统一鉴权&#xff0c;一般都是会放在网关中进行处理。目前非常流行的一种方案是使用JWT&#xff0c;详细的使用说明&#xff0c;可以找相关的资料查阅&#xff0c;这里先不进行深入的引用了…

鲸图知识图谱平台,助力金融业务深度洞察(上)

导语 大数据时代的背景下&#xff0c;数据早就成为数字经济重要的生产资料。对数据的挖掘能力成为企业数字化转型的驱动力。就金融行业来说&#xff0c;如果经营和管理方式跟不上大数据时代的发展脚步就会使得数据价值无法得到充分发挥。知识图谱作为一个结合了知识存储、知识…

SpringCloud入门概述;微服务入门概述

微服务入门概述 入门概述微服务分布式微服务架构Spring Cloud技术栈spring cloud各个组件的使用服务注册服务调用服务降级服务网关服务配置服务总线 参考 入门概述 auther JaneOnly date 2022/11/6 前置课程需要: java8mavengitNginxmqspringboot2.0 微服务 微服务架构就是一…

【阅读笔记】概率预测之DeepAR(含Pytorch代码实现)

本文作为自己阅读论文后的总结和思考&#xff0c;不涉及论文翻译和模型解读&#xff0c;适合大家阅读完论文后交流想法&#xff0c;关于论文翻译可以查看参考文献。论文地址&#xff1a;https://arxiv.org/abs/1704.04110 DeepAR 一. 全文总结二. 研究方法三. 结论四. 创新点五…