【数据结构】堆和优先级队列

news2024/11/18 12:33:39

目录

一、堆

1.1堆的特点

1.2如何构造一个最大堆

(1)最大堆的构造以及常用方法的实现

(2)添加操作

 (3)删除操作

(3)将任意数组调整为堆

 二、TopK问题

2.1使用优先级队列

(1)找最大元素

 (2)找最小元素


优先级队列:入队和队列是一样的默认都是队尾入队,出队的时候按照优先级出队,优先级相同的元素按照FIFO出队,优先级高的元素首先出队。

优先级队列的两大特点:

(1)按照优先级出队。

(2)优先级高的元素首先出队。

一、堆

堆是优先级队列背后的数据结构。

我们此处讲得堆其实是二叉堆,基于完全二叉树的二叉堆,广义的堆还有多叉堆(多叉树)。

还有索引堆,普通的对只知道堆顶元素的大小,索引堆可以取得堆中任意位置元素=》在图的最小生成树prim算法和最短路径Diijkstra算法的优化中使用索引堆。

1.1堆的特点

(1)堆首先是完全二叉树,一般使用动态数组(不用存储null结点,元素都靠左排序)存储具体元素。

(2)元素元素的值:

堆中任意一个节点的值<=父节点值(最大堆/大根堆)数据结点>=子树节点。

堆中任意一个节点的值>=父节点值(最小堆/小根堆)数据结点<=子树节点,JDK中的优先级队列默认是最小堆的实现。

在堆中,结点值的大小和节点所处的层次没有明确关系。

关于完全二叉树的节点编号:根据编号以及索引关系,在数据中寻找某一个结点的子节点与父节点编号和索引位置,都从0开始编号,若某个节点的编号(索引)为x。

性质1:判断父节点:

如何判定父结点存在,若存在,索引是多少?parent(x) = (x-1)/2。

当x>0,则一定存在父节点(在二叉树中,只有根节点(根节点索引为0)没有父节点),且父节点的编号为(x-1)/2。

性质2:判断左子树结点

如何判断当前节点x还存在左子树结点?左子树结点编号是多少》

当前完全二叉树的结点个数<=数组最大长度,左子树结点编号为2x+1<size(当前数组种有效的元素个数)。

性质3:判断右子树结点:

右子树结点编号为2x+2<size。

1.2如何构造一个最大堆

(1)最大堆的构造以及常用方法的实现

创建一个线性表的数组,然后创建一个int类型的size来判断存储了多少个数据。

默认状态数组长度为10,也可以通过有参构造自定义数组的长度。

创建三个判断父节点左右节点坐标的方法以及判空方法和输出方法。

public class MaxHeap {
    private List<Integer> arr;
    private int size;
    public MaxHeap() {
        this(10);
    }
    public MaxHeap(int capacity) {
        this.arr = new ArrayList<>(capacity);
    }
    private int parent(int k){
        return (k-1)>>1;
    }
    private int leftChild(int k){
        return (k<<1)+1;
    }
    private int rightChild(int k) {
        return (k << 1) + 2;
    }
    public boolean isEmpty() {
        return size == 0;
    }
    public String toString() {
        return arr.toString();
    }
}

(2)添加操作

尾插方法很简单直接调用线性表的add方法即可,然后size++,在调用我们的siftUp方法。

任意数组都可以看做一颗完全二叉树,因此向堆中插入一个数据,就相当于在数组中插入一个数据(默认尾插)。

siftUp(上浮)不断比较插入后的元素和其父节点的值大小关系,若当前节点值>父节点值,交换他俩元素顺序,直到这个元素落在了正确位置。结束条件:要么此时k==0,已经走到了树根节点,要么此刻arr[k]<=arr[parent[k]],当前上浮操作结束。

在创建一个swap方法,用来调换两个元素。

    public void add(int val){
        this.arr.add(val);
        size++;
        siftUp(size - 1);
    }
    private void siftUp(int k) {
        while(k>0&&arr.get(k)>arr.get(parent(k))){
            swap(k,parent(k));
            k = parent(k);
        }
    }
    private void swap(int i, int j) {
        int temp = arr.get(i);
        arr.set(i,arr.get(j));
        arr.set(j,temp);
    }
    public static void main(String[] args) {
        MaxHeap heap = new MaxHeap();
        heap.add(17);
        heap.add(11);
        heap.add(3);
        heap.add(2);
        heap.add(5);
        System.out.println(heap);
        heap.add(20);
        System.out.println(heap);
    }

一开始创建好的堆如下。

​ 然后要向尾部加入一个元素20。

然后调用siftUp方法,先将20与它的父结点值进行比较发现20更大那么就调用swap进行交换。

​然后继续比较父节点的值,发现还是20更大继续交换。

​ 到这时20已经没有父节点了,所以插入操作就结束了。

 (3)删除操作

extractMax():移除当前堆的最大值并返回。

最大堆的最大值在堆顶,删除树根之后,为了对其他子树影响最小,将数组的最后一个元素顶到当前二叉树的堆顶=》这个二叉树仍然是一颗完全二叉树。

需要进行运算的下沉操作siftDown,不断比较当前元素和子节点的最大值的关系,不断交换当前元素和子树的最大值,直到落到正确的位置。也有两个终止条件:1、此时元素已经落到了叶子结点)没有子树了。2、arr[k] >= arr[child[k]],大于左右子树的最大值,落在最终位置。

实际删除的也是数组的最后一个元素。

peekMax():查看当前最大堆的最大值元素。

    public int extractMax(){
        if (isEmpty()) {
            throw new NoSuchElementException("heap is empty!cannot extract!");
        }
        int k = arr.get(0);
        arr.set(0,arr.get(size-1));
        arr.remove(size-1);
        size--;
        siftDown(0);
        return k;
    }
    private void siftDown(int k) {
        while (leftChild(k) <size){
            int j = leftChild(k);
            if(j+1<size&&arr.get(j+1)>arr.get(j)){
                j = j+1;
            }
            if(arr.get(k)>=arr.get(j)){
                break;
            }else{
                swap(k,j);
                k = j;
            }
        }
    }
    public static void main(String[] args) {
        MaxHeap heap = new MaxHeap();
        heap.add(3);
        heap.add(2);
        heap.add(5);
        heap.add(17);
        heap.add(11);
        System.out.println(heap);
        heap.add(20);
        System.out.println(heap);
        System.out.println(heap.extractMax());
        System.out.println(heap);
    }

一开始插入操作得到的堆如下: 

 然后删除根节点,将最后一个元素补上。

 然后比较根节点3的左右子树获取最大值与根节点进行比较,右子树更大并且比根节点还打所以两个位置交换。

 这时3已经是叶子结点不能再继续比较,所以删除操作结束。

(3)将任意数组调整为堆

因为任意数组都可以看做是一棵完全二叉树,将完全二叉树进行适当的调整就可以得到一个堆。

从最后一个非叶子节点 开始进行元素的siftDown操作,直到根节点。(最后一个叶子节点的父节点就是最后一个非叶子节点)。

空间复杂度为O(n)。

    public MaxHeap(int[] num){
        this.arr = new ArrayList<>();
        for(int i:num){
            arr.add(i);
            size++;
        }
        for (int i = parent(size-1);i>=0;i--){
            siftDown(i);
        }
    }
    public static void main(String[] args) {
        int[] num = {3,2,17,11,20,5};
        System.out.println(new MaxHeap(num));
    }

 二、TopK问题

在一组非常大的数组中,找出前k个最大/最小的元素。

最能想到的方法就是先降序,然后依次取出前50个元素即可。时间复杂度是O(nlogn)。

2.1使用优先级队列

8字口诀:取大用小,取小用大。

这类问题的难点在于如何自定义“大小关系”,巧妙自定义类来定义大小关系,所有的TopK问题都可以使用相同的思路解决。

(1)找最大元素

如果是在100w个数据中心,找出前50个最大的元素。

就建立一个大小为50的最小堆,不断使用最小堆扫描整个数组,扫描完毕之后,最小堆中恰好能保存前50个最大元素。

1、当堆中元素<k,扫描元素直接保存到堆中。

2、当堆中元素个数>=k时,不断比较当前扫描的元素和堆顶元素的情况。

若当前扫描的元素<=堆顶元素,则这个元素小于当前堆中所有元素,直接跳过,一定不是要找的值。若扫描元素>堆顶元素,将堆顶元素移除,将当前元素添加到堆中。

扫描过程中不断地把最小堆的堆顶元素替换为越来越大的值。

3、当整个集合扫描完毕,堆中一定保存了最大的k各元素。

首先用前k各元素创建一个最小堆。

 然后走到元素9,比对顶元素大,则出队,然后入队。

  然后走到元素7,比对顶元素大,则出队,然后入队。

   然后走到元素6,比对顶元素大,则出队,然后入队。

 再走到元素2,比堆顶元素小则直接不入队,下一个元素5同理。然后走到元素8,比对顶元素大,则出队,然后入队。

 然后最后一个元素10,比对顶元素大,则出队,然后入队。到此优先级队列中的三个元素就是这10个元素里的前3个最大元素。

    public static void main(String[] args) {
        int[] arr = {1,4,3,9,7,6,2,5,8,10};
        int k = 3;
        Queue<Integer> queue = new PriorityQueue<>();
        for(int i : arr){
            if(queue.size()<k){
                queue.offer(i);
            }else{
                int top = queue.peek();
                if(i>top){
                    queue.poll();
                    queue.offer(i);
                }
            }
        }
        System.out.println(queue);
    }

 第二种解法:直接让所有的元素逐渐入队,如果queue长度超过k,就直接poll,就是移除对顶元素,也是扔出最小的元素,仍然留下k个元素。

    public static void main(String[] args) {
        int[] arr = {1,4,3,9,7,6,2,5,8,10};
        int k = 3;
        Queue<Integer> queue = new PriorityQueue<>();
        for(int i : arr){
            queue.offer(i);
            if(queue.size()>k){
                queue.poll();
            }
        }
        System.out.println(queue);
    }

 (2)找最小元素

就需要用到之前讲过的比较器,将JDK中默认的最大堆改为最小堆。

其他的都和最小堆的使用一样。

class IntDesc implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}
public class TopKTest {
    public static void main(String[] args) {
        int[] arr = {1,4,3,9,7,6,2,5,8,10};
        int k = 3;
        Queue<Integer> queue = new PriorityQueue<>(new IntDesc());
        for(int i : arr){
            queue.offer(i);
            if(queue.size()>k){
                queue.poll();
            }
        }
        System.out.println(queue);
    }
}

 更快的方法就是使用快速排序,可以在O(n)的时间复杂度找到k个元素。

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

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

相关文章

jenkins 参数化构建发布到kubernetes集群不同的命名空间下

最终效果 在构建前可以选择参数,要拉取的Git分支、什么命名空间下、什么服务。 自由风格项目使用的jenkins agent镜像,请参考这个文章: 【DevOps】(2022.11更新)基于jenkins/jnlp-slave:4.13.3-1-jdk11镜像,加入kubectl、mvn命令_阳光很暖吧的博客-CSDN博客 1、自由风格项…

广告和电商应该怎么串联起来呢?我们可以从各大巨头的动作中发掘

电商广告是广告产业与电子商务模式联姻的时代产物&#xff0c;是把广告、传媒、营销推广产业链的各种产品和服务搬到网上&#xff0c;利用网络便捷的实现广告资源信息流通、在线交易和客户关系管理的一种商业模式&#xff0c;是广告业营销模式和渠道的创新。 电商平台从无到有…

一篇文章彻底理解自定义View

目录 一.View的基础 1.view的基础概念 2.view的位置和事件event几种表示法 3.view的滑动 ①.ScrollTo、ScrollBy: ②.布局位置(layout,offsetLeftAndRight,offsetTopAndBottom) ③.布局参数(LayoutParams) 4.view的弹性滑动 ①.ScrollercomputeScrollscrollTo ②.动画…

如何基于YAML设计接口自动化测试框架?看完秒会

在设计自动化测试框架的时候&#xff0c;我们会经常将测试数据保存在外部的文件&#xff08;如Excel、YAML、CSV&#xff09;或者数据库中&#xff0c;实现脚本与数据解耦&#xff0c;方便后期维护。目前非常多的自动化测试框架采用通过Excel或者YAML文件直接编写测试用例&…

部署高校房屋管理系统可以实现哪些目标?

数图互通房产管理 随着技术的不断进步和升级&#xff0c;以及高校房屋建筑物数量的不断扩充&#xff0c;建立房屋资产管理信息系统进行信息化、数字化、图形化房屋资产管理已经是势在必行。数图互通自主研发的FMCenterV5.0平台&#xff0c;是针对中国高校房产的管理特点和管…

工业数据与数据采集应用如何在ARM+FPGA异核架构的米尔MYC-JX8MMA7核心板应用

随着通信与网络技术、互联网的发展&#xff0c;工业管理数据化、网络化、智能化已成大势所趋&#xff0c;利用工业物联网完成工业控制是智慧工厂中必不可少的一部分。传统的控制与数据采集系统&#xff0c;主机一旦需要同时与多个数据采集设备保持高速通信&#xff0c;并要承担…

Golang【Web 入门】 07 路由 - http.ServeMux

阅读目录说明ServeMux 和 Handler重构&#xff1a;区分不同的 Handler查看 http.HandleFunc 源码重构&#xff1a;使用自定义的 ServeMuxhttp.ServeMux 的局限性URI 路径参数请求方法过滤不支持路由命名http.ServeMux 的优缺点标准库里的就是最好的&#xff1f;说明 goblog 需…

【运维心得】ApacheDirectory找不到java路径的解决方案

目录 ApacheDirectory是什么&#xff1f; 问题现象描述 解决步骤 总结 本文是因为没有在网上找到类似的问题和文章&#xff0c;只能依靠自己去解决&#xff0c;既然解决了&#xff0c;就应该分享一下&#xff0c;希望能帮到需要的朋友。 ApacheDirectory是什么&#xff1f…

火山引擎 DataTester 揭秘:字节如何用 A/B 测试,解决增长问题的?

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 上线六年&#xff0c;字节跳动的短视频产品——抖音已成为许多人记录美好生活的平台。除了抖音&#xff0c;字节跳动旗下还同时运营着数十款产品&#xff0c;从资讯…

人工智能在网络安全中的重要性

介绍&#xff1a; 人工智能&#xff08;AI&#xff09;是计算机科学的一个分支&#xff0c;基于某些独特的算法和相关数学计算&#xff0c;使机器能够拥有人类的决策能力。另一方面&#xff0c;网络安全包括保护虚拟世界免受网络攻击和威胁的安全措施。人工智能能够通过采取与…

第二十一章《万年历》第1节:万年历项目简介

万年历项目实现的是一款日历软件,它能够展示出任意年份的日历,除此之外,该软件还能以红色字体标出每个月的阳历节日。 21.1.1万年历功能简介 万年历软件的运行结果如图21-1所示。 图21-1万年历软件界面 为方便讲述,此处把这个万年历的界面分成了4个区域,每个区域当中都有…

【优化发电】基于matlab差分进化算法求解单库发电优化问题【含Matlab源码 2253期】

⛄一、差分进化算法简介 如同所有的优化算法一样, 差分进化算法基于种群的进化算法。差分进化算法主要的参数主要有种群规模NP, 解空间的维数D, 缩放因子F和交叉概率Cr。D维矢量XGi[xGi,1, xGi,2, …, xi, DG], i1, 2, …, Np, 表示G代第i个个体。变异和交叉操作在每一代中产生…

NIO-ServerSocketChannel和Tomcat

ServerSocketChannel 面向流的侦听套接字的可选通道。 通过调用此类的open方法创建服务器套接字通道。 无法为任意预先存在的ServerSocket创建通道。 新创建的服务器套接字通道已打开但尚未绑定。 尝试调用未绑定的服务器套接字通道的accept方法将导致抛出NotYetBoundExcepti…

达梦数据库通过作业实现自动备份功能

达梦数据库通过作业实现自动备份功能作业功能简介一、通过DM管理工具创建备份作业(图形化配置)1.创建代理环境2.创建作业二、命令行方式配置备份作业案例1.创建代理环境2.全量备份3.增量备份4.备份清理三、JOB 运行和日志查看作业功能简介 在管理员的工作中&#xff0c;有许多…

安卓APP源码和设计报告——好再来点餐

大作业文档 项目名称&#xff1a;好再来点餐专业&#xff1a;班级&#xff1a;学号&#xff1a;姓名&#xff1a; 目 录 一、项目功能介绍3 二、项目运行环境3 1、开发环境3 2、运行环境3 3、是否需要联网3 三、项目配置文件及工程结构3 1、工程配置文件3 2、工程结构…

STC - 同时外挂扩展RAM和12864时, C库函数失效的问题

文章目录STC - 同时外挂扩展RAM和12864时, C库函数失效的问题概述笔记原理图 - 外挂XRAM原理图 - 12864错误现象总结ENDSTC - 同时外挂扩展RAM和12864时, C库函数失效的问题 概述 在写STC15实验箱4的出厂测试程序. 发现memset(buf, 0, 256)一片256字节的xdata内存时, 无法将这…

生产型企业如何搭建进销存管理系统?低代码平台了解一下

生产型企业在激烈的市场竞争中充分意识到信息化管理的重要性&#xff0c;但限于资金压力无法购买或开发大型的ERP 系统整合企业管理的小型企业而言&#xff0c;比较多的采用部署相对独立的小型信息系统提高管理信息化水平&#xff0c;常见的包括采购管理系统、销售管理系统、库…

Ubuntu开机自动挂载SD卡到指定挂载点并将Docker默认存储路径改为SD卡

Ubuntu开机自动挂载SD卡到指定挂载点并将Docker默认存储路径改为SD卡查看磁盘信息查看磁盘原挂载点永久开机自动挂载分区——修改文件/etc/fstab应用挂载修改docker默认存储路径查看磁盘信息 sudo fdisk -l如果磁盘太多可以用 sudo fdisk -l | grep GiB只看以GB为单位的磁盘&…

【D3.js】2.2-给 Circle 元素添加属性

title: 【D3.js】2.2-给 Circle 元素添加属性 date: 2022-12-02 15:19 tags: [JavaScript,CSS,HTML,D3.js,SVG] 上章节中虽然添加了circle&#xff0c;但是因为缺少某些属性设置而显得不可见&#xff0c;在此章节中将学习circle的cx、cy、r属性。 一、学习目标 circle的x坐标与…

带你玩转序列模型之NLP与词嵌入(一)

目录 一.词汇表征 二.使用词嵌入 三.词嵌入的特性 四.嵌入矩阵 五.学习词嵌入 一.词汇表征 上周我们学习了RNN、GRU单元和LSTM单元。本周你会看到我们如何把这些知识用到NLP上&#xff0c;用于自然语言处理&#xff0c;深度学习已经给这一领域带来了革命性的变革。其中一…