优先级队列(堆)  堆排序

news2024/11/25 12:33:13

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。 JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整!

堆的概念!

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质!

  1. 堆中某个节点的值,总是不大于或者不小于其父节点

  1. 堆总是一颗完全二叉树

小根堆(最小堆)

大根堆(最大堆)

从堆的概念,我们可以知道,堆是一颗完全二叉树,因此可以层序的规则,采用顺序的方式来高校存储!!

接下来,我们来思考一下:对于集合{27,15,19,18,28,34,65,49,25,37}中的数据,如何将其创建成堆呢??请看笔者接下来的代码:

public class TestHeap {
    public int[] elem;
    public int usedSize;

    public TestHeap(){
        this.elem=new int[10];
    }

    public void initElem(int[] array){
        for (int i = 0; i < array.length; i++) {
            elem[i]=array[i];
            usedSize++;
        }
    }

    public void createHeap(){
        for (int parent=(usedSize-1-1)/2;parent>=0;parent--){
            shiftDown(parent,usedSize);
        }
    }
    
    //通过向下调整的方式建立大根堆
    private void shiftDown(int parent,int len){
        int child=2*parent+1;
        //最起码要有左孩子
        while (child<len){
            //一定有左孩子的情况下
            if (child<len&&elem[child]<elem[child+1]){
                child++;
            }
            //child下标一定是左右孩子最大值的下标
            if (elem[child]>elem[parent]){
                int tmp=elem[child];
                elem[child]=elem[parent];
                elem[parent]=tmp;
                
                parent=child;
                child=2*parent+1;
            }else {
                break;
            }
        }
    }
}

那么,有了上述的方法,我们在main 方法中,有着:

  public static void main(String[] args) {
        TestHeap testHeap=new TestHeap();
        int[] array={27,15,19,18,28,34,65,49,25,37};
        testHeap.initElem(array);
        testHeap.createHeap();
    }

对于上述代码,若有不懂得,调试即可!!

另外,上述代码,最后建立的是大根堆!!若是想要建立小根堆,则改改代码即可(大于小于号的更改)

有了上述的基础,那么我们可以来根深入的了解一下:堆的插入!!

堆的插入!

在进行堆的插入之前,我们肯定会有一个堆,假设,我们已经有了一个堆,并且还是大根堆!!

当我们想要插入80,则放到当前这棵树的最后位置!!

放到最后位置处,我们还要再次调整为大根堆(向上调整)

通过比较当前插入的数据与根节点的大小,若比根节点大,则进行交换,然后在接着与根节点进行比较!!一直到小于根节点或者没有可比较的元素的时候停止!

在这里,我们主要进行的是:插入元素(向上调整)

    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;
    }
    
    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;
            }
        }
    }

经过上述的代码,我们可以看出:堆的插入总共需要两个步骤:

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容) 

  1. 将最后新插入的节点向上调整,直到满足堆的性质

接下来,我们进入:堆的删除环节!

堆的删除!

注意:堆的删除一定删除的是堆顶元素。具体如下: 1. 将堆顶元素对堆中最后一个元素交换 2. 将堆中有效数据个数减少一个 3. 对堆顶元素进行向下调整

    public void pop(){
        if (isEmpty()){
            return;
        }
        swap(elem,0,usedSize-1);
        usedSize--;
        shiftDown(0,usedSize);
    }
    
    public boolean isEmpty(){
        return usedSize==0;
    }
    
    private void swap(int[] array,int i,int j){
        int tmp=array[i];
        array[i]=array[j];
        array[j]=tmp;
    }

   //通过向下调整的方式建立大根堆
    private void shiftDown(int parent,int len){
        int child=2*parent+1;
        //最起码要有左孩子
        while (child<len){
            //一定有左孩子的情况下
            if (child<len&&elem[child]<elem[child+1]){
                child++;
            }
            //child下标一定是左右孩子最大值的下标
            if (elem[child]>elem[parent]){
                int tmp=elem[child];
                elem[child]=elem[parent];
                elem[parent]=tmp;

                parent=child;
                child=2*parent+1;
            }else {
                break;
            }
        }
    }

上述便是对于堆的所有代码,经过上面,我们也可以看出来:堆是建立在二叉树的基础上的!!所有,如果没有较好的二叉树基础,那么堆也是……白瞎!跟笔者这个小菜鸡一样!!

下面,我们来做几个练习题,巩固一下吧!!

面试题 17.14. 最小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))

经过前面内容的讲解,那么, 对于该题,想必能够迎刃而解了吧!!那么。接下来,请看笔者的代码吧:

    public int[] smallestk(int[] array,int k){
        int[] ret= new int[k];
        if (array==null || k==0){
            return ret;
        }
//建立小根堆
        Queue<Integer> minHeap =new PriorityQueue<>(array.length);
        for (int x: array) {
            minHeap.offer(x);
        }
        for (int i = 0; i < k; i++) {
            ret[i]=minHeap.poll();
//每次从小根堆中弹出一个元素,并且把这个元素放到ret[i]中
        }
        return ret;
    }

对于这个题目,其实难点还是在于思路!!

求最小K个数,我们需要建立大根堆!!将数据的前K个数,建立大根堆(堆顶元素是最大的元素),当遍历第K+1个元素时候,若小于堆顶元素,说明此时堆顶元素,一定不是前K个最小值,则进行交换!!

对于这些数据而言:27 15 19 18 28,根据前3个数据,建立大根堆:

但是,由于第4个元素,18比27小,所以,需要发生替换!!

前K个最大元素——建立小根堆!

小根堆中前K个最大的值,堆顶元素是这K个最大值里面最小的!当遍历到数组元素大于堆顶的时候,说明,此时堆顶的元素一定不是前K个最大的值!

那么,接下来,请看笔者的代码:

    //前K个最大的元素
    public static int[] maxK(int[] arr,int k){
        int[] ret=new int[k];
        if (arr==null || k==0){
            return ret;
        }
        
        Queue<Integer> minHeap=new PriorityQueue<>(k);
        //遍历数组的前K个,放到堆当中
        for (int i = 0; i < k; i++) {
            minHeap.offer(arr[i]);
            //忘最小堆中放入前K个元素
        }
        //遍历剩下的K-1个元素,每次和堆顶的元素进行比较
        //堆顶元素小的时候,堆出堆
        for (int i = 0; i < arr.length; i++) {
            int val=minHeap.peek();
            if (val<arr[i]){
                minHeap.poll();//出队列
                minHeap.offer(arr[i]);//把另一个元素放到堆里面
            }
        }
        for (int i = 0; i < k; i++) {
            ret[i]=minHeap.poll();
        }
        return ret;
    }

Top-k问题

TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。 对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都 不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下: 1. 用数据集合中前K个元素来建堆 前k个最大的元素,则建小堆 前k个最小的元素,则建大堆 2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

对于27,15,19,28,18,34,65,49,25,37这些数据而言,从小到大排序,那么,建立小根堆还是大根堆合适??要求对数组本身进行排序!!(显而易见的是大根堆合适)

小根堆的根节点是最小的,但是无法确定左右两个节点的大小,大根堆的做法是:先把数组变成一个大根堆,其次,再将0号下标元素与最后一个待排序的元素进行交换,然后在变为大根堆,此时最大的一个元素就在最后位置……以此类推,这就是堆排序!!

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

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

相关文章

【数据库】MySQL

时间戳 可以在创建表的时候&#xff0c;创建时间戳 mysql数据库怎么加入时间戳_帅气的黑桃J的博客-CSDN博客_mysql 插入时间戳 数据库表的命名规范 数据库表字段命名规范 - 腾讯云开发者社区-腾讯云 (tencent.com) MySql 的大小写问题 以下是MySQL详细的大小写区分规则&am…

《爆肝整理》保姆级系列教程python接口自动化(二十三)--unittest断言——上(详解)

简介 在测试用例中&#xff0c;执行完测试用例后&#xff0c;最后一步是判断测试结果是 pass 还是 fail&#xff0c;自动化测试脚本里面一般把这种生成测试结果的方法称为断言&#xff08;assert&#xff09;。用 unittest 组件测试用例的时候&#xff0c;断言的方法还是很多的…

Spring系列-9 Async注解使用与原理

背景&#xff1a; 本文作为Spring系列的第九篇&#xff0c;介绍Async注解的使用、注意事项和实现原理&#xff0c;原理部分会结合Spring框架代码进行。 本文可以和Spring系列-8 AOP原理进行比较阅读 1.使用方式 Async一般注解在方法上&#xff0c;用于实现方法的异步&#xf…

无源晶振匹配电容—计算方法

以前有写过一篇文章“晶振”简单介绍了晶振的一些简单参数&#xff0c;今天我们来说下无源晶振的匹配电容计算方法&#xff1a; 如上图&#xff0c;是常见的的无源晶振常见接法&#xff0c;而今天来说到就是这种常见电路的电容计算方法&#xff0c;有两种&#xff1a; A&#…

CUDA 内存系统

CUDA 内存系统 本文主要是针对<cuda c编程权威指南>的总结,由于原书出版的时候cuda刚刚出到cuda6,之后的cuda版本可能有更新,可能需要我翻一翻文档,待更新. 内存系统架构图 常见的内存作用域与生存期 新特性 早期的 Kepler 架构中一个颇为好用的特性就是 CUDA 程序员可…

没有公网ip怎么外网访问nas?快解析内网端口映射到公网

对于NAS用户而言&#xff0c;外网访问是永远绕不开的话题。拥有NAS后的第一个问题&#xff0c;就是搞定NAS的外网访问。不过众所周知&#xff0c;并不是所有的小伙伴都能得到公网IP&#xff0c;由于IPV4资源的枯竭&#xff0c;一般不会被分配到公网IP。公网IP在很大程度上除了让…

文件的打开关闭和顺序读写

目录 一、文件的打开与关闭 &#xff08;一&#xff09;文件指针 &#xff08;二&#xff09; 文件的打开和关闭 二、文件的顺序读写 &#xff08;一&#xff09;fputc 1. 介绍 2. 举例 &#xff08;二&#xff09;fgetc 1. 介绍 2. 举例1 3. 举例2 &#xff08;三&…

长尾关键词使用方法,通过什么方式挖掘长尾关键词?

当你在搜索引擎的搜索栏中输入有关如何使用长尾关键词的查询时&#xff0c;你可能希望有简单快捷的方式出现在搜索结果中&#xff0c;可以帮助你更好地应用seo。 不过&#xff0c;这里要记住一件事&#xff1a;SEO 策略只会为你的网站带来流量&#xff1b;在你的产品良好之前&a…

VS编译系统 实用调试技巧

目录什么是bug?调试是什么&#xff1f;有多重要&#xff1f;debug和release的介绍windows环境调试介绍、一些调试实例如何写出&#xff08;易于调试&#xff09;的代码编程常见的错误什么是bug?其实bug在英文翻译中有表示臭虫的含义&#xff0c;因为第一次被发现的导致计算机…

【Linux驱动开发100问】什么是模块?如何编写和使用模块?

&#x1f947;今日学习目标&#xff1a;什么是Linux内核&#xff1f; &#x1f935;‍♂️ 创作者&#xff1a;JamesBin ⏰预计时间&#xff1a;10分钟 &#x1f389;个人主页&#xff1a;嵌入式悦翔园个人主页 &#x1f341;专栏介绍&#xff1a;Linux驱动开发100问 什么是模块…

堆的基本存储

一、概念及其介绍堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵完全二叉树的数组对象。堆满足下列性质&#xff1a;堆中某个节点的值总是不大于或不小于其父节点的值。堆总是一棵完全二叉树。二、适用说明堆是利用完全二叉树的结构来维护一组数…

css 画图之质感盒子

前言 css 众所周知可以做很多的事情&#xff0c;比如&#xff1a;界面效果、特效、独特的样式等。今天给各位朋友带来的是以box-shadow来画一个很有质感效果的一个盒子。 之前在网上冲浪的时候&#xff0c;发现了这样的一个效果&#xff0c;所以来记录一下。 下面是实现后的…

Zookeeper源码环境搭建

前言 一、IEDA导入zk源码 git clone -b release-3.7.0 gitgithub.com:apache/zookeeper.git二、切换到稳定分支 通过命令行切换zk分支到3.8.1稳定版。 git checkout -b branch-3.8.1三、编译项目 执行maven命令编译项目 mvn clean install -Dmaven.test.skiptrue三、集群搭…

【计算机网络】高并发业务必备的Linux网络IO模型

IO的操作也就是应用程序从TCP缓冲区中读取数据的时候。网络I/O的本质是socket的读取&#xff0c;socket在linux中被抽象为流&#xff0c;I/O可以理解为对流的操作。对于一次I/O访问&#xff0c;数据会先被拷贝到操作系统的内核的缓冲区中&#xff0c;然后才会从操作系统内核的缓…

Java EE|TCP/IP协议栈之应用层协议DNS详解

文章目录一、对DNS的感性认识简介特点一些常见疑问二、DNSDNS域名结构域名的分级三、域名服务器四、域名解析过程参考一、对DNS的感性认识 简介 DNS&#xff0c;即Domain Name System,是域名系统的简称。它是Internet上解决网上机器命名的一种系统。 TCP/IP中的IP地址是由四…

C语言结构体对齐

1. 结构体对齐 要点 变量只能存储在他的长度的整数倍地址上结构体整体对齐跟他的最长的字段整数倍对齐 栗子1 struct Example1 {char a; //1个字节int c; //4个字节short b; //2个字节 };std::cout << sizeof(Example1 ) << std::endl; // 12 std::cout &…

JVM篇之垃圾回收

一.如何判断对象可以回收 1.引用计数法 只要一个对象被其他变量所引用&#xff0c;就让它的计数加1&#xff0c;被引用了两次就让它的计数变成2&#xff0c;当这个变量的计数变成0时&#xff0c;就可以被垃圾回收&#xff1b; 弊端&#xff1a;当出现如下图的情况&#xff0…

4.OCR文本识别Connectionist Temporal Classification(CTC)算法

文章目录1.基础介绍2.Connectionist Temporal Classification(CTC)算法2.1 什么是Temporal Classification2.2 CTC问题描述2.2关于对齐2.3 前向后向算法2.4 推理时3.pytorch中的CTCLOSS参考资料欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f3…

【react】react创建项目与引入AntD组件库:

文章目录一、初始化项目&#xff1a;【1】创建项目【2】暴露项目配置文件【3】安装依赖【4】配置less二、快捷键&#xff1a;【1】rcctab三、安装AntD组件库&#xff1a;【1】安装【2】index.js【3】问题&#xff1a;【4】效果&#xff1a;一、初始化项目&#xff1a; 【1】创…

【基于增强上下文注意网络:超分】

Enhanced Context Attention Network for Image Super Resolution &#xff08;基于增强上下文注意网络的图像超分辨率&#xff09; 深度卷积神经网络&#xff08;CNN&#xff09;极大地提高了图像超分辨率&#xff08;SR&#xff09;的性能。尽管图像随机共振的目标是恢复高频…