数据结构:优先级队列(堆)

news2024/11/19 12:33:57

概念

优先级队列是啥? 

队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列。
在这种情况下, 数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue)

堆是啥?

优先级队列的底层运用到堆这种数据结构

堆的特点:总是一棵完全二叉树

大根堆:

每一棵树的根结点总是比左右子节点大

小根堆:

每一棵树的根结点的值总是比左右子节点小,不考虑左右子节点谁大谁小

堆的存储

存储方式采用层序遍历的方式把二叉树的元素一一放到数组里面

那数组可不可以存储非完全二叉树呢?答案是可以的,但是会有空间浪费的情况

像上面右边图,4的位置没有存储元素,这就是一种空间浪费

来手搓一个堆

回顾一下二叉树里面性质的第五点

如何将普通数组转换成堆 

把下面数组的元素画成堆

先画成一棵普通的二叉树

画成大根堆

28<37,互换。最左边子树49>25>18,把49变成该树的根结点,最右边的树65>34>19,也进行交换

调整第二层的树,49>15>37,49作为根,而15<18<25,下方的树得把25变成根

最上面一层的树,65>49>27,65做根结点,而27<34,所以34还得作为该子树的根结点

这就是一个大根堆了

总结:

1.从最后一棵子树开始调整

2.在要变换的树里面,从左右孩子里面找到最大的与根结点比较,大了就进行互换

3.如果能够知道子树根结点下标,那么下一棵子树就是当前根结点下标-1

4.一直调整到0下标这个树为止

先写个初步的代码

public class TestHeap {
    private 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++;
        }
    }

问题

1.最后一棵子树根结点下标是多少

因为i = len-1,所以根结点index = (i-1)/2

    public void createHeap(){
        //usedSize-1相当于最后一棵树孩子结点的下标i,再-1是为了求父结点
        for (int parent = (usedSize-1-1)/2; parent >= 0; parent--) {
            siftDown(parent,usedSize);
        }
    }

2.每棵子树调整完之后,结束的位置怎么定?也就是我要从哪里开始调整下一棵子树? 

我们采用向下调整的方法(注意,虽然我们是从最后一颗树往根结点方向调整,但是每一棵树的处理我们还是采用从父结点到子节点的调整方法。为什么用不向上调整?后面我会说到。)

找到最后一个元素置为c,其根结点为p

调整完后不知道下面还有没有元素要调整,所以c还得往下走 

此时c的坐标是19 > 10了,所以可以停止了

    private void siftDown(int parent, int len){
        int child = 2 * parent + 1;
        while(child < len){
            //左右孩子比较大小
            if(elem[child] < elem[child + 1]){
                child = child + 1;
            }
            //走完上面的if,证明child下标一定是左右两个孩子最大值的下标

        }
    }

现在问题来了,写到这里会发生数组越界,如果我的child移到9下标这里,那这个if判断elem[child] < elem[child+1] 这里的child+1 = 10 = usedSize,而这棵树根本就没有10这个下标,造成了越界 

修改一下代码

            if(child+1<len && elem[child] < elem[child + 1]){
                child = child + 1;
            }

后面就是比较孩子和父结点的代码了

   /**
     * 向下调整
     * @param parent
     * @param len
     */
    private void siftDown(int parent, int len){
        int child = 2 * parent + 1;
        while(child < len){
            //左右孩子比较大小
            if(child+1<len && elem[child] < elem[child + 1]){
                child = child + 1;
            }
            //走完上面的if,证明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;//不用比不用调了
            }
        }
    }

测试一下,没有问题😊

怎么计算这个堆的时间复杂度?

考虑最坏情况,就是满二叉树的情况

首先明确一点,最后一层结点时不进行调整的,一般是从倒数第二层结点开始调整的

设树的高度是h

T(N) = (h-1)*2^0+(h-2)*2^1+(h-3)*2^2+......+2*2^(h-3)+1*2^(h-2)

怎么求这个等式?采用错位相减

根据等比求和公式

T(n) = 2 ^ h - 1 - h

因为n=2^h-1    --> h = log(n+1)

代进去T(n) = n - log(n+1)

因为log(n+1)的图长这样,n越大越趋于一个常数

所以整个等式占支配地位的还得是n,所以T(N) ≈ n  -->时间复杂度:O(N)


堆的插入

如果插入的数值比较小

如果插入的数值比较大,那就得一层一层进行调整

这种调整叫做向上调整

    public void swap(int i, int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
    public void push(int val){
        if(isFull()){
            elem = Arrays.copyOf(elem, 2*elem.length);
        }
        elem[usedSize] = val;
        //向上调整
        siftUp(usedSize);
        usedSize++;
    }
    //判断满不满
    public boolean isFull(){
        return usedSize == elem.length;
    }
    public void siftUp(int child){
        int parent = (child - 1) / 2;
        while(child>0){
            if(elem[child] > elem[parent]){
                swap(child,parent);
                child = parent;
                parent = (child - 1) / 2;
            }else{
                break;
            }
        }
    }

在测试里面把80push进去,没有问题😊

堆的插入的时间复杂度

因为最坏情况插入的元素是最大的,那这个元素最多也就向上调整到根节点的位置,也就是h

复杂度就是O(logN) 

欸那为什么不用向上调整来建堆呢?😐

我们分析一下,拿这棵满二叉树来说,最底层有8个元素,已经占了一半了,网上建堆得每个元素都遍历一遍,时间复杂度太大了

 

 

堆的删除

因为堆的删除一定是删除优先级最高的值,所以一定是删除大根堆的根结点

比如这个,我们要做的就是删除65

第一步:把65(0下标)与28(最后一个元素)进行交换

第二步:向下调整0下标

    public int pop(){
        if(empty()){
            throw new EmptyException("数组空了!");
        }
        int oldVal = elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown(0,usedSize);
        return oldVal;
    }

    public boolean empty(){
        return usedSize == 0;
    }

测试一下,没有问题😊

习题:

选A(可以自己画图,反正就是层序遍历画树)

选C

 

总共比较3次,左边那个15的原本就是小根堆,所以就不用比较

选C

PriorityQueue

Java集合框架提供了PriorityQueue的优先级队列

注意事项:

        PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>();
        priorityQueue1.offer(new Student("zhangsan",10));
        priorityQueue1.offer(new Student("lisi",12));

 1.PriorityQueue放入的元素必须能比较大小,否则会报出下面的错误 

2.不能插入null对象,否则会报出下面的错误

        PriorityQueue<Student> priorityQueue1 = new PriorityQueue<>();
        priorityQueue1.offer(null);

 

3.没有容量限制,可以插入任意多个元素,内部会自动扩容

4.插入和删除都是O(logn) 

5.使用了最小堆的数据结构,所以每次获取的元素都是最小的元素

oj练习

面试题 17.14. 最小K个数 - 力扣(LeetCode)

设计一个算法,找出数组中最小的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))

方法一:

建立最小堆,把堆顶k个元素输出出来就行了

代码

    public int[] smallestK(int[] arr, int k) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();

        //向上调整 O(logN)
        for (int i = 0; i < array.length; i++) {
            priorityQueue.offer(array[i]);
        }
        int[] ret = new int[k];
        //k*logN
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

虽然通过了,但是时间复杂度有点大 

方法二:

1.建立大根堆,大小为k,比如我们可以拿前三个元素来建一个大根堆

2.从第k+1个元素开始比较,如果比堆顶元素小,则入堆。当前的堆顶元素(较大的)就舍弃掉,因为已经不符合我对前k个最小的元素的要求了

遍历完整个大根堆长这样

问题来了,PriorityQueue是默认采用小根堆的底层,那我们要怎么让它采用大根堆呢

PriorityQueue源码里面的有一个compare函数

这个函数外层是compareTo函数

这两个函数结合一下,把小的放在前面,大的放在后面,所以实现了小根堆的底层

我们可以重写PriorityQueue里面的compare函数,把大的放在前面

class Imp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}

整个的代码(上面的重写可以扔到匿名内部类里面)

    public static int[] smallestK(int[] array, int k) {
        int[] ret = new int[k];
        if(array == null || k <= 0) {
            return ret;
        }
        //匿名内部类
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });

        //1、建立大小为k的大根堆 O(K*logK)
        for (int i = 0; i < k; i++) {
            priorityQueue.offer(array[i]);
        }

        //2、遍历剩下的元素 (N-K)*logK
        // (K*logK) + N*logK  - K*logK   =   N*logK  -->时间复杂度
        for (int i = k; i < array.length; i++) {
            int top = priorityQueue.peek();//27
            if(array[i] < top) {
                priorityQueue.poll();
                priorityQueue.offer(array[i]);
            }
        }
        //下面这个不能算topK的复杂度 这个地方是整理数据
        //k*logK
        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }
        return ret;
    }

 

别看力扣上面的通过时间,我们要自行分析时间复杂度 


堆排序

把这个数组从小到大排序,需要建立大根堆

再把这棵树放到堆底,这样最大的元素就有序了

再按照大根堆进行排序(已经有序的元素就不管了),把最大元素49放到堆顶,然后再和堆第的15交换

以此类推,设置一个堆底end,每次拿0下标的元素和它交换,交换完end--

    public void heapSort(){
        int end = usedSize-1;
        while(end>0){
            swap(0,end);
            siftDown(0,end);
            end--;
        }
    }

时间复杂度O(N*logN)

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

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

相关文章

在线主动学习算法评估策略:prequential evaluation procedure

在线主动学习算法评估策略&#xff1a;prequential evaluation procedure 在在线主动学习领域(Online Active Learning)&#xff0c;对在线主动学习算法的评估策略有多种方法&#xff0c;而现如今常用的方法是prequential evaluation procedure(出自论文《High density-focuse…

计算机网络与技术——数据链路层

&#x1f60a;计算机网络与技术——数据链路层 &#x1f680;前言☃️基本概念&#x1f94f;封装成帧&#x1f94f;透明传输&#x1f94f;差错检测 ☃️点对点协议PPP&#x1f94f;PPP协议的特点&#x1f94f;PPP协议的帧格式&#x1f50d;PPP异步传输时透明传输&#xff08;字…

git-更新项目

进入想要更新的项目目录下&#xff0c;按住shift右击&#xff0c;点击open Git Bash here进入命令行 项目日志 git log 拉取代码 git pull origin developer 将工作区的文件添加到暂存区 git add . 查看文件状态&#xff08;更改信息&#xff09; git status 提交暂存区到…

【多线程】线程互斥 {多执行流并发执行的数据竞争问题,互斥锁的基本用法,pthread_mutex系列函数,互斥锁的原理;死锁;可重入函数和线程安全}

一、进程线程间通信的相关概念 临界资源&#xff1a;多线程执行流共享的资源就叫做临界资源。确切的说&#xff0c;临界资源在同一时刻只能被一个执行流访问。临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区。互斥&#xff1a;通过互…

检测和缓解SQL注入攻击

SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严&#xff0c;攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句&#xff0c;在管理员不知情的情况下实现非法操作&#xff0c;以此来实现欺骗数据库服务器执行非授权的任意查询&#…

一文搞懂 LiveData 粘性事件 和 数据倒灌

文章目录 前言LiveData使用分析粘性事件分析数据倒灌总结 一、前言 在使用LiveData的时候&#xff0c;会有疑问&#xff0c;LiveData 是怎么做到先发送再注册&#xff0c;依然能够接收到事件的。还有就是会碰到切换屏幕&#xff0c;导致重复的操作&#xff0c;也就是所谓的数…

postgresql的windows

1. 资源下载&#xff1a; https://www.postgresql.org/download/windows/ 2. 安装 双击&#xff0c;指定D盘目录&#xff0c;接下来默认安装&#xff0c;一直到出现下面的最后一步。一定要去除勾选复选框。 在最后&#xff0c;点击FINISH。 3. 初始化 4. 检查和修改配置 1&am…

MaxQuant的安装和_使用_(linux)

MaxQuant 是一款定量蛋白质组学软件包&#xff0c;支持多种标记定量和无标定量的质谱数据。 1. 安装 MaxQuant下载方式&#xff1a; 通过官网 下载&#xff0c;包括andromeda&#xff08;搜索引擎&#xff09;viewer&#xff08;检查原始数据、鉴定和定量结果&#xff09;。通…

使用c语言与EASYX实现弹球小游戏

eg1:小球碰到窗体的四个墙实现反弹效果 #include <stdio.h> #include <easyx.h> #include <iostream> #include <math.h> #include <conio.h> #define PI 3.14int main() {initgraph(800, 600);setorigin(400, 300);setaspectratio(1, -1);setb…

MWeb Pro for Mac:博客生成编辑器,助力你的创作之旅

在当今数字化时代&#xff0c;博客已经成为了许多人记录生活、分享知识和表达观点的重要渠道。而要打造一个专业、美观且易于管理的博客&#xff0c;选择一款强大的博客生成编辑器至关重要。今天&#xff0c;我向大家推荐一款备受好评的Mac软件——MWeb Pro。 MWeb Pro是一款专…

从龙湖智创生活入选金钥匙联盟,透视物业服务力竞争风向

假设你是业主&#xff0c;物业“服务”和“管理”&#xff0c;哪个名词看起来更加亲切、讨喜&#xff1f; 站在个人角度&#xff0c;“服务”更让人感受到温度。但对于一个要长期运营下去的住宅或者商企项目来说&#xff0c;整体的管理又必不可少。前者面向人&#xff0c;后者…

Zigbee 模组拉距测试

1.测试方法&#xff1a; 两个同型号Zigbee模组组网&#xff0c;一个作为协调器&#xff0c;一个作为路由器&#xff0c;协调器设备负责组网&#xff0c;其中路由器节点作为被测设备&#xff0c;入网网成功后一分钟开始一轮测试&#xff0c;连续发送100包数据&#xff0c;每包数…

nginx: [emerg] bind() to 0.0.0.0:18888 failed (98: Unknown error)问题解决办法

周末断网&#xff0c;今天来了之后&#xff0c;nginx出现这个问题&#xff0c;本站基本搜索的都是端口被占用问题&#xff0c;我试着杀掉所有占用端口的进程&#xff0c;解决办法 1.killall -9 nginx 2.然后启动(./nginx)nginx&#xff08;PS&#xff1a;不要./nginx -s relo…

echarts 饼图标注 字体修改

option {grid: {// 四周留白区域设置top: 20,right: 20,left: 20,bottom: 20,containLabel: true,},tooltip: {trigger: item,formatter: {b} : {c} ({d}%) // 展示百分比},series: [{type: pie,radius: [40%, 60%],avoidLabelOverlap: false,startAngle: 0, //起始角度data: …

Ajax学习笔记第4天

做决定之前仔细考虑&#xff0c;一旦作了决定就要勇往直前、坚持到底&#xff01; 【1 模仿百度招聘】 整个流程展示&#xff1a; 1.文件目录 2.页面效果展示及代码 data中的page1数据展示 2.1 主页 index.html:index里面代码部分解释 underscore.js :模板页面的相关代码 &…

香橙派OrangePi Zero开发板的WiFi连接

文章目录 调试串口连接连接WIFI设置开机自动连接自定义设置固定IP地址远程SSH连接 调试串口连接 1、准备一个 3.3v 的USB转TTL的模块&#xff0c;将开发板连接到电脑上 注意&#xff1a;引脚连接 a. USB 转 TTL 模块的 GND 接到开发板的 GND 上b. USB 转 TTL 模块的 RX 接到开…

专业课改革,难度陡然提高,专业课122总分390+南京理工大学818南理工818上岸经验分享

今年专业课相对较难&#xff0c;分数122&#xff0c;基本达到预期。南理工818是信号和数电两门课&#xff0c;各站一半。复试时间数电可能要更多一点&#xff0c;也比信号难拿分。今年专业课难度很大&#xff0c;基本超过不少985学校&#xff0c;大家要重视。 有条件的同学建议…

建筑施工二维码预制件管理系统

随着科学经济技术的发展&#xff0c;二维码标识设备越来越受人们的喜爱&#xff0c;不仅仅是在人们日常购物时需要使用到&#xff0c;在建筑用预制构件中使用也非常普遍&#xff0c;在这里二维码相当于预制构件的身份证&#xff0c;替代了传统标识牌。 1、信息管理。通过凡尔码…

倾斜摄影三维模型的顶层合并构建重要性分析

倾斜摄影三维模型的顶层合并构建重要性分析 倾斜摄影超大场景的三维模型的顶层合并对于构建精确、完整且真实的三维模型具有重要的意义和应用价值。本文将从几个方面对其重要性进行浅析。 一、模型完整性与连贯性 倾斜摄影超大场景的三维模型的顶层合并可以将多个倾斜摄影数据…

Tesseract-OCR的安装与环境变量配置

网盘链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;gtp1 中文包网盘链接&#xff1a;小蓝枣的资源仓库&#xff0c;提取码&#xff1a;8v8u 安装步骤&#xff1a;解压后直接“NEXT”&#xff0c;“NEXT”注意选择自己的安装路径。 安装后查看是否安装成功&#…