Java优先级队列-堆

news2024/9/23 19:27:40

Java优先级队列-堆

  • 💐1. 二叉树的顺序存储💐
    • 🎃 1.1 存储方式🎃
    • 👻1.2 下标关系👻
  • 🌸2. 堆(heap)🌸
    • 🌞2.1 概念🌞
    • 🌝2.2 操作-向下调整🌝
    • 🌚2.3操作-向上调整🌚
    • 🌐2.4 操作-建堆🌐
  • 🌷3. 堆的应用-优先级队列🌷
    • 📕3.1 概念📕
    • 📗3.2 内部原理📗
    • 📘3.3 操作-入队列📘
    • 📙3.4 操作-出队列(优先级最高)📙
    • 📓3.5 返回队首元素(优先级最高)📓
    • 📔3.6 代码📔
    • 📒3.7 java 中的优先级队列📒
  • 🍀4. 堆的其他应用-TopK 问题🍀
  • 🌹 5. 面试题🌹
  • 🌻6. 堆的其他应用-堆排序🌻

大家好,我是晓星航。今天为大家带来的是 Java优先级队列(堆) 的讲解!😀

💐1. 二叉树的顺序存储💐

🎃 1.1 存储方式🎃

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。

一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。

这种方式的主要用法就是堆的表示。

👻1.2 下标关系👻

已知双亲(parent)的下标,则:

左孩子(left)下标 = 2 * parent + 1;

右孩子(right)下标 = 2 * parent + 2;

已知孩子(不区分左右)(child)下标,则:

双亲(parent)下标 = (child - 1) / 2;

🌸2. 堆(heap)🌸

🌞2.1 概念🌞

  1. 堆逻辑上是一棵完全二叉树
  2. 堆物理上是保存在数组中
  3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
  4. 反之,则是小堆,或者小根堆,或者最小堆:每根二叉树的根结点都小于左右孩子节点
  5. 堆的基本作用是,快速找集合中的最值

🌝2.2 操作-向下调整🌝

**前提:**左右子树必须已经是一个堆,才能调整。

说明:

  1. array 代表存储堆的数组
  2. size 代表数组中被视为堆数据的个数
  3. index 代表要调整位置的下标
  4. left 代表 index 左孩子下标
  5. right 代表 index 右孩子下标
  6. min 代表 index 的最小值孩子的下标

过程(以小堆为例):

  1. index 如果已经是叶子结点,则整个调整过程结束

    1. 判断 index 位置有没有孩子
    2. 因为堆是完全二叉树,没有左孩子就一定没有右孩子,所以判断是否有左孩子
    3. 因为堆的存储结构是数组,所以判断是否有左孩子即判断左孩子下标是否越界,即 left >= size 越界
  2. 确定 left 或 right,谁是 index 的最小孩子 min

    1. 如果右孩子不存在,则 min = left
    2. 否则,比较 array[left] 和 array[right] 值得大小,选择小的为 min
  3. 比较 array[index] 的值 和 array[min] 的值,如果 array[index] <= array[min],则满足堆的性质,调整结束

  4. 否则,交换 array[index] 和 array[min] 的值

  5. 然后因为 min 位置的堆的性质可能被破坏,所以把 min 视作 index,向下重复以上过程

图示:

// 调整前
int[] array = { 27,15,19,18,28,34,65,49,25,37 };
// 调整后
int[] array = { 15,18,19,25,28,34,65,49,27,37 };

时间复杂度分析:

最坏的情况即图示的情况,从根一路比较到叶子,比较的次数为完全二叉树的高度

即时间复杂度为 O(log(n))

代码:

/**
 * 向下调整函数的实现
 * @param parent 每棵树的根节点
 * @param len 每棵树的调整的结束位置
 */
public void shiftDown(int parent,int len) {
    int child = parent * 2 + 1;
    //至少有一个左孩子
    while (child < len) {
        if (child + 1 < len && elem[child] < elem[child + 1]) {
            child++;//保证为左右子节点的最大值
        }
        if (elem[child] > elem[parent]) {
            int tmp = elem[child];
            elem[child] = elem[parent];
            elem[parent] = tmp;
            parent = child;
            child = parent * 2 + 1;
        } else {
            break;
        }
    }
}

🌚2.3操作-向上调整🌚

同理向上调整的代码为:

/**
 * 向上调整函数实现
 * @param child 添加的子树来进行判断调整
 */
private void shiftUp(int child) {
    int parent = (child - 1) / 2;
    while (child > 0) {
        if (elem[child] > elem[parent]) {
            int tmp = elem[child];
            elem[child] = elem[parent];
            elem[parent] = tmp;
            child = parent;
            parent = (child - 1) / 2;
        } else {
            break;
        }
    }
}
public void offer (int val) {
    if (isFull()) {
        //扩容
        elem = Arrays.copyOf(elem,2*elem.length);
    }
    elem[usedSize++] = val;
    shiftUp(usedSize - 1);
}
public boolean isFull() {
    return usedSize == elem.length;
}

此代码为传入一个新的子树,然后进行向上调整 即offer一个新元素,在添加完毕后我们将其按照大堆排好。

🌐2.4 操作-建堆🌐

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

图示(以大堆为例):

// 建堆前
int[] array = { 1,5,3,8,7,6 };
// 建堆后
int[] array = { 8,7,6,5,1,3 };

时间复杂度分析:

粗略估算,可以认为是在循环中执行向下调整,为 O(n * log(n))

(了解)实际上是 O(n)

堆排序中建堆过程时间复杂度O(n)怎么来的?

代码:

public static void createHeap(int[] array, int size) {
    for (int i = (size - 1 - 1) / 2; i >= 0; i--) {
        shiftDown(array, size, i);
    }
}

🌷3. 堆的应用-优先级队列🌷

📕3.1 概念📕

在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。

在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

📗3.2 内部原理📗

优先级队列的实现方式有很多,但最常见的是使用堆来构建。

📘3.3 操作-入队列📘

过程(以大堆为例):

  1. 首先按尾插方式放入数组
  2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
  3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤
  4. 直到根结点

图示:

代码:

public static void shiftUp(int[] array, int index) {
    while (index > 0) {
        int parent = (index - 1) / 2;
        if (array[parent] >= array[index]) {
            break;
        }

        int t = array[parent];
        array[parent] = array[index];
        array[index] = t;

        index = parent;
    }
}

📙3.4 操作-出队列(优先级最高)📙

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆

第一步:交换0下标和最后一个元素

第二步:调整0下标这棵树就可以了

📓3.5 返回队首元素(优先级最高)📓

返回堆顶元素即可

📔3.6 代码📔

public class MyPriorityQueue {
    // 演示作用,不再考虑扩容部分的代码
    private int[] elem = new int[100];
    private int usedSize;

public void offer (int val) {
    if (isFull()) {
        //扩容
        elem = Arrays.copyOf(elem,2*elem.length);
    }
    elem[usedSize++] = val;
    shiftUp(usedSize - 1);
}
public boolean isFull() {
    return usedSize == elem.length;
}
public int poll() {
    if (isEmpty()) {
        throw new RuntimeException("优先级队列为空!");
    }
    int tmp = elem[0];
    elem[0] = elem[usedSize - 1];
    elem[usedSize - 1] = tmp;
    usedSize--;
    shiftDown(0,usedSize);
    return tmp;
}
public int peek() {
    if (isEmpty()) {
        throw new RuntimeException("优先级队列为空!");
    }
    return elem[0];
}
public boolean isEmpty() {
    return usedSize == 0;
}
}

📒3.7 java 中的优先级队列📒

PriorityQueue implements Queue

🍀4. 堆的其他应用-TopK 问题🍀

拜托,面试别再问我TopK了!!!

关键记得,找前 K 个最大的,要建 K 个大小的小堆

topK问题:给你100万个数据,让你找到前10个最大的元素。

思路一:对整体进行排序,输出前10个最大的元素。

思路二:用堆,即弹出10个最大的元素即可

思路三:

1、先把前3个元素,创建为小根堆

2、当前的堆为什么是小根堆,因为堆顶的元素,一定是当前K个元素当中最小的一个元素。如果有元素X比堆顶元素大,那么X这个元素,可能就是topK中的一个。

相反,如果是大根堆,那就不一定了

3、如果堆顶元素小,那么出堆顶元素,然后入当前比堆顶元素大的元素,再次调整为小根堆

时间复杂度:n*logn

总结:

1、如果要前K个最大的元素,要一个小根堆。(方便弹出堆顶的最元素,然后重新排序为小根堆)

2、如果要前K个最小的元素,要一个大根堆。(方便弹出堆顶的最元素,然后重新排序为大根堆)

3、第K的元素。建一个根堆,堆顶元素就是第K的元素,堆内的元素就是前K个最大的元素。(X比堆顶元素大就放进去,然后重新排序为小根堆)

4、第K的元素。建一个根堆,堆顶元素就是第K的元素,堆内的元素就是前K个最小的元素。(X比堆顶元素小就放进去,然后重新排序为大根堆)

例题:求前K个最小的元素:

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 晓星航
 * Date: 2023-04-12
 * Time: 19:07
 */
public class TopK {
    /**
     * 求数组当中的前K个最小的元素
     * @param array
     * @param k
     * @return
     */
    public static int[] topK(int[]array, int k) {
        //1、创建一个大小为K的大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        //2、遍历数组当中的元素,前k个元素放到队列当中
        for (int i = 0; i < array.length; i++) {
            if (maxHeap.size() < k) {
                maxHeap.offer(array[i]);
            } else {
                //3、从第k+1个元素开始,每个元素和堆顶元素进行比较
                int top = maxHeap.peek();
                if (top > array[i]) {
                    //3.1先弹出
                    maxHeap.poll();
                    //3.2后存入 注:offer时我们的堆重新自动排序为大根堆
                    maxHeap.offer(array[i]);
                }
            }
        }
        //创建数组tmp来依次接受我们的maxHeap大根堆排序好的值
        int[] tmp = new int[k];
        for (int i = 0; i < k; i++) {
            tmp[i] = maxHeap.poll();
        }
        return tmp;
    }
    public static void main(String[] args) {
        int[] array = {18,21,8,10,34,12};
        int[] tmp = topK(array,4);
        System.out.println(Arrays.toString(tmp));
    }
}

🌹 5. 面试题🌹

查找和最小的K对数字

import java.util.*;
class Solution {
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
                PriorityQueue<List<Integer>> maxHeap = new PriorityQueue<>(k, new           Comparator<List<Integer>>() {
            @Override
            public int compare(List<Integer> o1, List<Integer> o2) {
                return (o2.get(0)+o2.get(1)) - (o1.get(0)+o1.get(1));
            }
        });
        for (int i = 0; i < Math.min(nums1.length,k); i++) {
            for (int j = 0; j < Math.min(nums2.length,k); j++) {
                if (maxHeap.size() < k) {
                    List<Integer> tmpList = new ArrayList<>();
                    tmpList.add(nums1[i]);
                    tmpList.add(nums2[j]);
                    maxHeap.offer(tmpList);
                } else {
                    int top = maxHeap.peek().get(0) + maxHeap.peek().get(1);
                    if (top > nums1[i] + nums2[j]) {
                        maxHeap.poll();
                        List<Integer> tmpList = new ArrayList<>();
                        tmpList.add(nums1[i]);
                        tmpList.add(nums2[j]);
                        maxHeap.offer(tmpList);
                    }
                }
            }
        }
        List<List<Integer>> ret = new ArrayList<>();
        for (int i = 0; i < k && !maxHeap.isEmpty(); i++) {
            ret.add(maxHeap.poll());
        }
        return ret;
    }
}

🌻6. 堆的其他应用-堆排序🌻

问:如果要将堆从小到大进行排列,我们应该选择大根堆还是小根堆来进行排序?

答:使用大根堆来进行排序、

思路如下:

1、调整为大根堆

2、0小标和最后1个未排序的元素进行交换即可

由上图可知当所有元素弹出后,我们的数组便自动排成了从小到大的顺序。

3、end–

感谢各位读者的阅读,本文章有任何错误都可以在评论区发表你们的意见,我会对文章进行改正的。如果本文章对你有帮助请动一动你们敏捷的小手点一点赞,你的每一次鼓励都是作者创作的动力哦!😘

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

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

相关文章

SER | 语音情绪识别中的TIM-NET_SER项目复现

大家好&#xff0c;今天复现的是目前语音情绪识别的SOTA论文&#xff0c;论文中文名称是 时间建模的重要性&#xff1a; 用于语音情感识别的新型时空情感建模方法 。论文中训练的数据集有英文德语等几个语音情绪识别中常见的语音情绪数据集&#xff0c;以对比精度权重等效果~各…

Android 下一代架构指南:DDD

移动端架构与网站架构的区别是什么&#xff1f;网易新闻客户端的架构演进历程是怎样的&#xff1f;为什么要选择 DDD 思想来指导重构&#xff1f;DDD 落地中应当关注哪些方面&#xff1f;带着这些问题我们来看下文。&#xff08;节选自网易新闻App架构重构实践&#xff09; 当…

Kafka吞吐量

目录 kafka的架构和流程 小文件对HDFS影响&#xff1a; 解决办法&#xff1a; kafka的架构和流程 ⾸先Kafka从架构上说分为⽣产者Broker和消费者,每⼀块都进⾏了单独的优化,⽐如⽣产者快是因为数据的批量发送&#xff0c;Broker快是因为分区,分区解决了并发度的问题,⽽且⽂…

媒体宣传的优势与重要性

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 媒体宣传日益成为企业和品牌宣传推广的重要手段&#xff0c;媒体的宣传报道更有权威性&#xff0c;能够帮助品牌进行背书&#xff0c;更有权威性&#xff0c;另外媒体的报道在搜索引擎中…

基于GPS/北斗卫星技术的无盲区车辆调度系统

基于GPS/北斗卫星技术的无盲区车辆调度系统 现代车辆调度系统是一种集全球卫星定位技术&#xff08;GPS&#xff09;、地理信息技术&#xff08;GIS&#xff09;和现代通信技术于一体的高科技项目。它将移动目标的动态位置&#xff08;经度与纬度&#xff09;、时间和状态等信息…

linux环境搭建jmeter、ant、git、Jenkins、jdk、Tomcat

我在搭建环境时&#xff0c;将jmeter、ant、jdk、Tomcat都放在陆opt文件夹下 1.下载jmeter、ant、Jenkins&#xff08;Jenkins.war包&#xff09;、jdk、Tomcat Linux环境下安装Jenkins&#xff0c;需要jdk版本大于11 2.环境配置 jdk配置 vim /etc/profile 添加配置信息&am…

飞书接入ChatGPT - 将ChatGPT集成到飞书机器人,直接拉满效率 【飞书ChatGPT机器人】

文章目录 前言环境列表视频教程1.飞书设置2.克隆feishu-chatgpt项目3.配置config.yaml文件4.运行feishu-chatgpt项目5.安装cpolar内网穿透6.固定公网地址7.机器人权限配置8.创建版本9.创建测试企业10. 机器人测试 前言 在飞书中创建chatGPT机器人并且对话,在下面操作步骤中,使…

LBS找外贸客户 外贸怎么找客户

随着全球贸易的不断发展&#xff0c;越来越多的企业开始寻找更多的客户和销售机会。而随着移动互联网的普及&#xff0c;LBS已经成为了人们生活和工作中不可或缺的一部分。在商业领域中&#xff0c;LBS被广泛应用于定位、导航、营销等方面&#xff0c;为企业提供了更加便捷、精…

如何在Mac VM Fusion上安装和使用Plan 9

我在 Mac 上使用 VM Fusion 安装 Plan 9 的时候遇到了很多问题&#xff0c;官方文档和有些前两年的国外的一些博客并没有写清楚&#xff0c;甚至出现了“误导”的情况&#xff08;有些情况变了&#xff09;。所以来写本文帮助其他也遇到的问题的人。 如果你能看到这篇博客&…

上传ChatGPT相关资源,瓜分¥5000元奖金池

一、活动时间 资源类型时间上传地址上传【ChatGPT的原理分析】资源4月17日-4月30日https://upload.csdn.net/creation/uploadResources?taskId643925fde212675bb64a3984&utm_sourceblog上传【Chatgpt的多种使用方法】资源4月15日-4月30日https://upload.csdn.net/creatio…

介绍NPOI 的颜色卡、名称以及索引

文章目录 前言 遍历NPOI颜色 前言 使用NPOI的颜色时&#xff0c;一些颜色类的名称很难想象出具体对应的颜色&#xff0c;所以有了下面的对照表&#xff0c;方便使用。 NPOI 颜色的索引范围是 8~64,超出范围无效。 色彩类名索引Index名称#000000HSSFColor.Black8黑色#ffffffH…

【C++ 一】C++ 入门、数据类型、运算符

C 入门、数据类型、运算符 文章目录 C 入门、数据类型、运算符前言1 C 初识1.1 第一个C程序1.1.1 创建项目1.1.2 创建文件1.1.3 编写代码1.1.4 运行程序 1.2 注释1.3 变量1.4 常量1.5 关键字1.6 标识符命名规则 2 数据类型2.1 整型2.2 sizeof 关键字2.3 实型&#xff08;浮点型…

【最详细最完整】windows 安装 Oracle Java环境

windows 安装Oracle Java环境 一、安装教程二、验证Java环境 前言&#xff1a;公司有个app的项目&#xff0c;我是打算使用uniapp来实现&#xff0c;那么调试是需要使用到java环境&#xff0c;所以我本地就得安装java环境&#xff0c;接着我找了好多文章发现没有相对完整的&…

在头部大厂做了13年云计算后,这次他想系统地聊聊FinOps!

随着企业上云战略的深入普及&#xff0c;越来越多的企业开始关注云成本优化。伴随着企业对IT资源的投入不断增加&#xff0c;企业迫切需要解决成本与效率&#xff0c;以及如何将云成本优化落到实处的问题。 FinOps是将财务和业务整合到一起的变革&#xff0c;可以帮助企业更好…

抖音数字人主播app

抖音数字人主播app是指一款利用计算机生成的虚拟数字人&#xff0c;在抖音平台上进行实时音视频传输和互动的应用程序。该软件可以让用户创建自己的虚拟数字人&#xff0c;并在抖音平台上进行实时互动和交流。 抖音数字人主播app通常需要包含以下功能&#xff1a; 3D建…

本地JAR打镜像,并启动

1.准备好jar&#xff0c;和Dokerfile文件。 2.使用命令打镜像 docker build -t wstest . 3. 查看镜像 4. 由于服务是两个端口。使用以下命令 5.优化怎么随着docker的开启而启动 docker run --restartalways -p 8089:8089 -p 8069:8069 wsserver docker run --restartalways -…

C++基础入门——语法详解篇(下)

文章目录 一、缺省参数 1、1 缺省参数的概念 1、2 缺省参数的分类 1、2、1 全部缺省 1、2、2 半缺省参数 二、引用 2、1 引用的概念 2、2 引用特征 2、3 引用的使用场景 2、3、1 引用做参数 2、3、2 常引用 2、3、3 引用做返回值 2、4 引用总结 三、内联函数 3、1 内联函数的引…

谷歌浏览器的跨域设置、配置、新老版本Chrome

文章目录 1、个人开发中的使用习惯2、老版本Chrome浏览器(版本号49之前)3、新版本Chrome浏览器(版本号49之后) 1、个人开发中的使用习惯 下载好谷歌浏览器以后&#xff0c;快捷方式一份放在桌面上&#xff0c;一份放在开始菜单栏&#xff0c;桌面的重命名为dev(可以随意命名)&a…

【09 cookie and session】

cookie and session 一、cookie1. 会话技术2. 什么是cookie3. cookie的属性4. cookie方法5. cookie添加和获取6. 需求案例7. cookie的细节 二、session1. HttpSession介绍2. HttpSession常用方法3. HttpSession获取4. HttpSession使用5. HttpSession使用细节 一、cookie cooki…

【MongoDB】什么是MongoDB?MongoDB有什么特点?MongoDB的适用场景?

什么是MongoDB数据库&#xff1f; MongoDB是一个开源、高性能、支持海里数据存储的文档型数据库。 MongoDB是一个高效的非关系型数据库&#xff08;不支持表关系&#xff1a;只能操作单表&#xff09; MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系…