Java -数据结构,【优先级队列 / 堆】

news2024/11/19 5:51:47

一、二叉树的顺序存储

在前面我们已经讲了二叉树的链式存储,就是一棵树的左孩子和右孩子
而现在讲的是:顺序存储一棵二叉树。

1.1、存储方式

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中。 一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费。
这种方式的主要用法就是堆的表示
在这里插入图片描述

下标关系

已知双亲(parent)的下标,则:
左孩子(left)下标 = 2 * parent + 1;
右孩子(right)下标 = 2 * parent + 2;

已知孩子(不区分左右)(child)下标,则:
双亲(parent)下标 = (child - 1) / 2;

也就是前面我们将的性质5:
对于具有n个结点的完全二叉树,如果按照从上至下从左至右的顺序对所有节点从0开始编号,则对于序号为i的结点有:

(1)若i>0,双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点
(2)若2i+1<n,左孩子序号:2i+1,否则无左孩子
(3)若2i+2<n,右孩子序号:2i+2,否则无右孩子
在这里插入图片描述

二、堆

2.1、概念

  1. 堆逻辑上是一棵完全二叉树
  2. 堆物理上是保存在数组中
  3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
  4. 反之,则是小堆,或者小根堆,或者最小堆
  5. 堆的基本作用是,快速找集合中的最值 :无论是 大根堆还是小根堆, 它们的 最值【最大值 和 最小值】都处于 二叉树的 根结点处。要想获得 最值,直接 peek 方法,就能获得 树 的 根结点值 / 最值。在这里插入图片描述
    在这里插入图片描述

2.2、操作-向下调整

前提:左右子树必须已经是一个 堆 / 逻辑上是一棵完全二叉树。
将一组 记录完全二叉树数据 的 数组 转换成 大根堆。
在这里插入图片描述

向下调整出现的问题:

这里是引用
得出结论:其实每棵树的调整结束位置都是一样的︰不能超过数组长度。

如何构造一个 向下调整的函数 - 重点
在这里插入图片描述

public class TestHeap {
    public int[] elem;//底层是一个数组
    public int usedSize;

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

    /**
     * 创建堆
     * @param array 堆里面存放的元素
     */
    public void creatHeap(int[] array){
        //将array数组的元素存入elme 数组
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i] ;
            usedSize++;
        }
        for (int praent = (usedSize-1-1)/2; praent >= 0; praent--) {
            shiftDown(praent,usedSize);
        }
    }

    /**
     * 向下调整
     * @param praent 每棵子树的父亲节点
     * @param len 调整的结束位置,不能大于数组的长度
     */
    public void shiftDown(int praent, int len){
        int child = 2+praent +1;
        while (child < len){
            if(child + 1 > len && this.elem[child] < this.elem[child+1]){
                child++;
            }

            if(elem[child] > elem[praent]){
                int tmp = elem[child];
                elem[child] = elem[praent];
                elem[praent] = tmp;

            }else {
                break;
            }
        }
    }
}

测试一下:
在这里插入图片描述

模拟实现 堆 的 时间复杂度
在这里插入图片描述
上图转载于:堆 / 优先队列

粗略估算,可以认为是在循环中执行向下调整,为 O(n * log(n))
(了解)实际上是 O(n)
堆排序中建堆过程时间复杂度O(n)怎么来的?

2.3、操作-建堆

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

图示(以大堆为例):

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

这里是引用

三、堆的应用-优先级队列

3.1、概念

在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次
高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这
种数据结构就是优先级队列(Priority Queue)

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

3.2、堆的基本操作

我们知道堆分为大根堆和小根堆,那Java中自带的默认是大根堆还是小根堆???
Java集合中默认是小根堆

我们测试一下:

public static void main(String[] args) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        priorityQueue.offer(12);
        priorityQueue.offer(3);
        priorityQueue.offer(18);

        System.out.println(priorityQueue.poll());
        System.out.println(priorityQueue.poll());

    }

在这里插入图片描述

优先级队列 - 模拟实现入队 – offer()
在这里插入图片描述

/**
     *  插入元素
     * @param val
     */
    public void offer(int val){
        if(isFull()){
            //扩容
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        //没有满就将val放在数组的最后一个元素
        elem[usedSize] = val;
        usedSize++;

        //然后就调整堆,使其成为一个大根堆
        shiftUp(usedSize-1,val);
    }


    /**
     *
     * @param child 孩子节点的坐标
     * @param val 要插入的值
     */
    public void shiftUp(int child, int val){
        int praent = (child - 1) / 2;

        while(praent > 0){
            if(elem[child] > elem[praent]){
                int tmp = elem[child];
                elem[child] = elem[praent];
                elem[praent] = tmp;

                //然后再改变child 和 praent 的指向
                child = praent;
                praent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }

    public boolean isFull(){
        return this.elem.length == usedSize;
    }

在这里插入图片描述

优先级队列 - 模拟实现出队 – poll()
在这里插入图片描述

public int poll(){
        if(isFull()){
            throw new RuntimeException("队列为null!");
        }
        //先将0下标和数组的最后一个元素交换
        int tmp = elem[0];
        elem[0] = elem[usedSize-1];
        elem[usedSize-1] = tmp;
        usedSize--;

        //然后向下调整0下标这棵树
        shiftDown(0,usedSize);
        return tmp;
    }
    //判断是否为null
    public boolean isEmpty(){
        if (usedSize == 0){
            return true;
        }
        return false;
    }

总程序

public class Heap {
    public int[] elements;// 底层数组
    public int usedSize;// 有效元素个数
    // 构造方法
    public Heap(){
        // 数组初始化容量
        this.elements = new int[10];
    }
     // 创建堆,获取 输入数组 的 数据
    public void creationHeap(int[] array){
        this.usedSize += array.length;
        if(isFull()){
            this.elements = Arrays.copyOf(this.elements,this.elements.length*2);
        }
        this.elements = Arrays.copyOf(array,array.length);

        for(int parent = (this.usedSize -1 - 1)/2 ;parent >= 0;parent--){
            // 向下调整
            shiftDown(parent,this.usedSize);
        }
    }
    // 向下调整
    public void shiftDown(int parent,int len){
        int child = parent * 2 + 1;// 左孩子
        // 能进入该循环,说明 这个 parent 只少有一个孩子。
        while(child < len){
            // 获取 左右孩子的最大值
            if(child+1 < len &&this.elements[child] < this.elements[child+1]){
                child++;
            }
            // 判断 孩子最大值 是否 比  双亲节点 val 值 大
            // 如果大,就需要进行交换
            if(this.elements[child] > this.elements[parent]){
                int tmp = elements[child];
                elements[child] = elements[parent];
                elements[parent] = tmp;

                // 见附图
                parent = child;
                child = parent * 2 + 1;
            }else{
                break;
            }
        }
    }

    // 入队操作
    public void offer(int val){
        if(isFull()){
            // 扩容
            this.elements = Arrays.copyOf(this.elements,this.elements.length * 2);
        }
        elements[usedSize++] = val;
        //usedSize++;
        shiftUp(usedSize-1);// 有效元素个数 是 usedSize,最后一个元素的下标是 usedSize -1
    }
    private void shiftUp(int child){
        int parent = (child - 1)/2;
        while(child > 0){
            if(this.elements[child] > this.elements[parent]){
                int tmp = this.elements[child];
                this.elements[child] = this.elements[parent];
                this.elements[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else{
                break;
            }
        }
    }
    // 判断队列满没满
    public boolean isFull(){
        return this.usedSize >= this.elements.length;
    }

    // 出队操作
    public int poll(){
        if(isEmpty()){
            throw new RuntimeException("优先级队列为空!");
        }
        int tmp = this.elements[0];
        this.elements[0] = this.elements[this.usedSize -1];
        this.elements[this.usedSize - 1] = tmp;
        this.usedSize--;
        shiftDown(0,usedSize);
        return tmp;
    }

    // 判断队列 空不空
    public boolean isEmpty(){
        return this.usedSize == 0;
    }
    public int peek(){
        if(isEmpty()){
            throw new RuntimeException("优先级队列为空!");
        }
        return this.elements[0];
    }

}

四、校招 – TopK问题

问题描述:

从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。(从100万中找出最大的k个数)

栗子:

从arr[1, 12]={5,3,7,1,8,2,9,4,7,2,6,6} 这n=12个数中,找出最大的k=5个。

在这里插入图片描述
总结

1、如果求前K个最大的元素,要建一个小根堆。
2、如果求 前K个最小的元素,要建一个大根堆。
3、如果是求第k大的元素,建一个小堆,小根堆 堆顶的元素就是第k大的元素。
4、如果是求第k小的元素,建一个大堆,大根堆 堆顶的元素就是第k小的元素。

五、堆的其他应用-堆排序

1、将数据调整为 大根堆、
2、0 下标 与 最后一个未排序的元素进行交换即可。
3、循环上述两个操作,直至 最后一个未排序的元素 下标为 0.。

在这里插入图片描述

/**
     * 堆排序
     */
    public void heapSort(){
        int last  = usedSize - 1;
        while (last  > 0){
            int tmp = elem[0];
            elem[0] = elem[last ];
            elem[last ] = tmp;
            shiftDown1(0,last);
            last --;
        }
    }

    /**
     *向下调整
     * @param parent
     * @param len
     */
    public void shiftDown1(int parent,int len){
        int child = parent * 2 + 1;// 左孩子
        // 能进入该循环,说明 这个 parent 只少有一个孩子。
        while(child < len){
            // 获取 左右孩子的最大值
            if(child+1 < len &&this.elem[child] < this.elem[child+1]){
                child++;
            }
            // 判断 孩子最大值 是否 比  双亲节点 val 值 大
            // 如果大,就需要进行交换
            if(this.elem[child] > this.elem[parent]){
                int tmp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = tmp;
                
                parent = child;
                child = parent * 2 + 1;
            }else{
                break;
            }
        }
    }

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

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

相关文章

真实需求和梦想实现满足

多少的时光和岁月中都不曾认真系统的深度思考自己的真实需求和欲望之间是否一致&#xff0c;随着时间的流逝才发现自己追求的是一场空&#xff0c;自己的真实需求并不是苦苦追求的东西&#xff0c;这也是当梦想照进现实&#xff01;欲望是无善无恶的&#xff0c;不必为了满足自…

性能测试——LoadRunner: virtual user generator的使用

LoadRunner 在安装时取消勾选指定LoadRunner代理将要使用的证书&#xff0c;安装完成后会显示下面三个软件 Vitual User Generator&#xff1a;生成性能测试脚本Controller&#xff1a;创建测试场景&#xff0c;运行测试脚本&#xff0c;监控运行&#xff0c;收集到运行的数…

Spring——AOP是什么?如何使用?

一、什么是AOP&#xff1f;在不修改源代码的情况下 增加功能二、底层是什么&#xff1f;动态代理aop是IOC的一个扩展功能&#xff0c;现有IOC&#xff0c;再有AOP&#xff0c;只是在IOC的整个流程中新增的一个扩展点而已&#xff1a;BeanPostProcessorbean的创建过程中有一个步…

【JAVA】List接口

&#x1f3c6;今日学习目标&#xff1a;List接口 &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰本期期数&#xff1a;第四期 &#x1f389;专栏系列&#xff1a;JAVA List接口一、ArrayList二、LinkedList总结一、ArrayList ArrayLis…

python完美实现一个自己的音乐服务器

最近发现&#xff0c;经常用的网易云音乐&#xff0c;有很多歌曲下架了&#xff0c;能听的越来越少了&#xff1b;歌单里的一些歌曲&#xff0c;现在要开通 VIP 才能听了。其实自己常听的歌曲不是很多&#xff0c;现在却有很多听不了了。怎么办呢&#xff0c;付费吗&#xff1f…

Python进阶-----面对对象5.0(面对对象三大特征之--多态)

目录 前言&#xff1a; 多态 习题 前言&#xff1a; 上一期讲了Python面对对象中的继承&#xff0c;而今天讲的是多态&#xff0c;其实多态跟继承是紧密相关的&#xff0c;换句话说多态是继承的一种表现形式&#xff0c;下面就一起来看看吧&#xff01;&#xff08;上一期链…

性价比高的骨传导蓝牙耳机,推荐几款性能高的骨传导耳机

​骨传导耳机&#xff0c;顾名思义是一种声音传递方式&#xff0c;利用头骨作为震动传导发声。不像一般耳机那样通过外耳或内耳传递声音。声音由耳部传播到头后产生振动刺激颅脑内听觉中枢引起听觉。因此是一种非入耳式的声音传播方式。而在选购过程中&#xff0c;对于价格、功…

自动驾驶目标检测项目实战(二)—基于Faster-RCNN的交通标志检测

自动驾驶目标检测项目实战——基于Faster-RCNN的交通标志检测 目前目标检测算法有很多&#xff0c;流行的就有faster-rnn和yolov&#xff0c;本文使用了faster-rnn框架进行训练&#xff0c;效果还不错&#xff0c;准确率&#xff0c;当然也可以使用更高版本的Yolov进行实战。本…

RK3568触摸屏驱动调试总结

硬件电路分析 RK3568 CPU通过I2C与触控板外设wdt87xx连接。 首先要根据电路图获取如下I2C的信息&#xff1a; 项目Value接在哪个I2Ci2c1I2C 寄存器地址0x2cHID 地址0x20中断B5 1、接在哪个I2C 如图,1接在I2C1&#xff1a; 2、使用哪个GPIO引脚接收触控板的中断 如图&#xf…

Ubantu从0开始配置深度学习RTX 4090+3090显卡的服务器

文章目录1. 基础2. 用户访问3. Pytorch环境的问题4. 显卡调度问题方法一&#xff1a;在shell命令前强制指定显卡方法二&#xff1a;在代码中强制指定显卡5. 各种各样的小BUG5.1 Liunx创建新用户登录异常&#xff1a;/usr/bin/xauth: error/timeout in locking authority file /…

Unity之向量计算

文章目录前言向量加法向量减法向量乘法/除法向量点乘&#xff08;内积&#xff09;向量叉乘&#xff08;外积&#xff09;向量归一化向量小结前言 讲讲Unity中的向量有关知识&#xff0c;一些概念在初高中就学过&#xff0c;就不解释了。向量只能与自己相同维度进行计算&#…

Zookeeper3.5.7版本——选举机制(第一次启动时)

目录一、第一次启动服务时Zookeeper的选举机制1.1、服务器1启动1.2、服务器2启动1.3、服务器3启动1.4、服务器4启动1.5、服务器5启动二、Zookeeper中的一些概念了解2.1、SID2.2、ZXID2.3、Epoch一、第一次启动服务时Zookeeper的选举机制 1.1、服务器1启动 服务器1启动&#x…

嵌入式学习笔记——STM32硬件基础知识

STM32开发硬件知识前言STM32最小系统电源电路晶振电路复位电路BOOT选择电路调试接口电路其他电路本文重点本文参考博客链接前言 上一篇中我们重点是讲了一下怎么搭建开发环境以及怎么下载烧录的过程&#xff0c;这都是解决的电脑端的开发环境问题&#xff0c;还没有到实际的开…

【数据结构】邻接矩阵和邻接图的遍历

写在前面 本篇文章开始学习数据结构的图的相关知识&#xff0c;涉及的基本概念还是很多的。本文的行文思路:学习图的基本概念学习图的存储结构——本文主要介绍邻接矩阵和邻接表对每种结构进行深度优先遍历和广度优先遍历先识概念话不多说&#xff0c;狠活献上学习思想等等&…

C++ Stack栈学习

1. stack的介绍和使用1.1 stack的介绍1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行元素的插入与提取操作。2. stack是作为容器适配器被实现的&#xff0c;容器适配器即是对特定类封装作为其底层的容器&…

sync map思考

sync map 作为解决 map 并发读写问题的补充&#xff0c;用法上其实不复杂&#xff0c;有些惋惜的是&#xff0c;不支持 len 统计数量的方法。map 并发读写算得上一个非常严重的问题&#xff0c;会导致服务宕机&#xff0c;为了避免 map 的并发读写&#xff0c;一种解决办法是直…

华为机试题:HJ108 求最小公倍数(python)

文章目录&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;Python3实现&#xff08;3&#xff09;知识点详解1、input()&#xff1a;获取控制台&#xff08;任意形式&#xff09;的输入。输出均为字符串类型。1.1、input() 与 list(input()) 的区别、及其相互转换方…

软件工程知识-软件测试

1、软件测试是发现软件错误&#xff08;缺陷&#xff09;的主要手段&#xff1a; 从是否关系软件内部结构和具体实现的角度对软件测试进行分类 2.静态测试&#xff1a;以检查为主&#xff08;桌前检查、代码走查、代码审查&#xff09; 动态测试&#xff1a;实际运行程序&am…

Leetcode刷题一

目录序言树「结构」「遍历」「经验」「跨父节点」「题型」序言 笔记根据labuladong进行总结&#xff0c;极力推荐labuladong算法进行学习&#xff01;&#xff01; 树 0、算法一开始就应该刷树&#xff0c;了解递归的思想。 1、C语言中定义了一个结构体&#xff0c;然后申明…

通过指针引用数组的几种方法的原理和差异;以及利用指针引用数组元素的技巧

关于地址&#xff0c;指针&#xff0c;指针变量可以参考这篇文章&#xff1a; 地址&#xff0c;指针&#xff0c;指针变量是什么&#xff1f;他们的区别&#xff1f;符号&#xff08;*&#xff09;在不同位置的解释&#xff1f;_juechen333的博客-CSDN博客https://blog.csdn.n…