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

news2024/12/26 10:36:17

文章目录

    • 1.优先级队列概念 💮
    • 2.优先级队列的模拟实现💮
    • 3.常用接口PrinrityQueue介绍💮
    • 4.堆的应用💮

1.优先级队列概念 💮

优先级队列 :是不同于先进先出队列的另一种队列。每次从队列中取出的是具有最高优先权的元素。

优先级队列相对于普通队列应该提供两个最基本的操作
(1)返回最高优先级对象(2)添加新的对象

2.优先级队列的模拟实现💮

在JDk1.8中的优先级队列底层使用了堆,而堆实际就是在完全二叉树的基础上进行了一些调整。

2.1堆的概念💮

堆这种数据结构本质上就是一个完全二叉树
并且堆中某个结点的值总是不大于或不小于其父结点的值

小堆:根节点最小的堆,满足Ki <= K2i+1 且 Ki <= K2i+2

大堆:根节点最大的堆, 满足Ki >= K2i+1 且 Ki >= K2i+2

在这里插入图片描述

🌹 堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

2.2堆的存储方式💮

堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储
对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节 点,就会导致空间利用率比较低。

i为孩子节点,双亲节点为 (i - 1)/2
i为根节点,左孩子下标为2 * i + 1,右孩子下标为2 * i + 2

2.3堆的创建💮

2.3.1堆向下调整(以创建大堆为例)💮

分析过程如下:
在这里插入图片描述

     //向下调整
        private void shiftDown(int parent, int len) {
            int child = 2 * parent + 1;
            //最起码要有左孩子
            while (child < len) {
                //child+1<len的设置是为了防止child越界异常
                //获得左右孩子的最大值
                if (child + 1 < len && elem[child] < elem[child + 1]) {
                    child++;
                }
                //child下标一定是左右孩子  最大值的下标
                if (elem[child] > elem[parent]) {
                    int temp = elem[child];
                    elem[child] = elem[parent];
                    elem[parent] = temp;
                    //继续向下调整
                    parent = child;
                    child = 2 * parent + 1;
                } else {
                    break;
                }
            }
        }

然后通过循环每个结点,向下调整,然后创建好这棵树

     public void createHeap() {
            //usedSize-1表示最后一个child,(usedSize-1-1)/2表示最后一个父节点
            for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
                shiftDown(parent, usedSize);
            }
        }

时间复杂度分析
第1层需要向下移动h-1层
第2层需要向下移动h-2层
…依次类推

分析过程如下:


在这里插入图片描述
2.4堆的插入与删除💮
2.4.1堆的插入💮
分析过程如下:

这里以在大根堆前提下插入80为例:

在这里插入图片描述

代码如下:

//向上调整
    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;//11
        //向上调整
        shiftUp(usedSize - 1);//10
    }

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

2.4.2堆的删除💮
分析过程如下:
在这里插入图片描述
代码如下:

需要判断堆中元素是否为空的情况

//删除元素
    public void pop() {
        if (isEmpty()) {
            return;
        }
        swap(elem, 0, usedSize - 1);
        usedSize--;
        shiftDown(0, usedSize);
    }

    public void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

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

选择题练习

4.最小堆[0,3,2,5,7,4,6,8],在删除堆顶元素0之后,其结果是( C )
A: [3,2,5,7,4,6,8] B: [2,3,5,7,4,6,8]
C: [2,3,4,5,7,8,6] D: [2,3,4,5,6,7,8]
在这里插入图片描述

3.常用接口PrinrityQueue介绍💮

3.1PrinrityQueue的特性

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线
程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue。

在这里插入图片描述

使用注意:

  1. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出ClassCastException异常

  2. 不能插入null对象,否则会抛出NullPointerException

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

  4. 插入和删除元素的时间复杂度为

  5. PriorityQueue底层使用了堆数据结构

  6. PriorityQueue默认情况下是小堆 —即每次获取到的元素都是最小的元素

3.2 PriorityQueue常见的几种构造方式
在这里插入图片描述

static void TestPriorityQueue(){ 
// 创建一个空的优先级队列,底层默认容量是11  
  PriorityQueue<Integer> q1 = new PriorityQueue<>(); 
// 创建一个空的优先级队列,底层的容量为initialCapacity
  PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
  ArrayList<Integer> list = new ArrayList<>(); 
  list.add(4); 
  list.add(3); 
  list.add(2);
   list.add(1); 
   // 用ArrayList对象来构造一个优先级队列的对象 
   // q3中已经包含了三个元素  
   PriorityQueue<Integer> q3 = new PriorityQueue<>(list); 
   System.out.println(q3.size()); 
   System.out.println(q3.peek());
    }

默认情况下,PriorityQueue队列是小堆,如果需要大堆需要提供比较器

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{ @Override
public int compare(Integer o1, Integer o2) { 
     return o2-o1;
     } 
 }
public class TestPriorityQueue {
public static void main(String[] args) { 
   PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
   p.offer(4); 
   p.offer(3);
   p.offer(2);
   p.offer(1); 
   p.offer(5);
   System.out.println(p.peek()); 
     } 
  }

此时创建出来的就是一个大堆。

插入删除/获取优先级最高的元素
在这里插入图片描述
练习:top-k问题

描述:求前k个最大的数

适用情况: 在数据量比较大时,求数据集合中前K个最大的元素或者最小的元素

思路:

1)求前K个最大的元素,建立大小为K的小根堆
2)然后用剩下的集合里面的元素轮流和堆顶元素比较,如果剩下集合里面的元素比堆顶的元素大,那就替换掉堆顶的元素
3)然后向下调整,变成新的小根堆,此时这个堆中的元素就是前K个最大元素

代码如下:

public class Test {
    //前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 * logK + (N-K) * logK = NlogK


        //时间复杂度:K * logK
        //1.遍历数组的前k个 放到堆当中
        for (int i = 0; i < k; i++) {
            minHeap.offer(arr[i]);
        }
        //2.遍历剩下的K-1个,每次和堆顶元素进行比较
        //堆顶元素 小的时候,就出堆
        //时间复杂度 : (N-K) * logK
        for (int i = k; 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;

    }

    public static void main(String[] args) {
        int[] array = {1, 5, 43, 3, 2, 7, 98, 41, 567, 78};
        int[] ret = maxK(array, 3);
        System.out.println(Arrays.toString(ret));

    }
}

如果要求前K个最小的元素,如何做?

和前面差不多,不同的是
(1)求前K个最小的元素,要建立大根堆
(2)比较的时候谁小,就把小的放在堆顶

面试题17.14.求前k个最小数-力扣(LeetCode)

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if (arr == null || k == 0) {
            return ret;
        }
        //PriorityQueue默认建立小根堆,但是这里我们要建立大根堆,就需要自己实现比较器
        Queue<Integer> minHeap = new PriorityQueue<>(k,new  Comparator<Integer>() {
            @Override
            //o1-o2就是小根堆
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });

        //总的时间复杂度:K * logK + (N-K) * logK = NlogK


        //时间复杂度:K * logK
        //1.遍历数组的前k个 放到堆当中
        for (int i = 0; i < k; i++) {
            minHeap.offer(arr[i]);
        }
        //2.遍历剩下的K-1个,每次和堆顶元素进行比较
        //堆顶元素 大的时候,就出堆
        //时间复杂度 : (N-K) * logK
        for (int i = k; 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;

    }
}

4.堆的应用💮

4.1 PriorityQueue的实现

用堆作为底层结构封装优先级队列

4.2堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆
  • 升序:建大堆
  • 降序:建小堆
  1. 利用堆删除思想来进行排序
    即将第一个元素与最后一个元素交换,从上往下调整为大根堆/小根堆,最后输出就是一个有序序列

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

这里要按照从小到大排序,就建立大根堆

 public static void heapSort(int[] array) {
        createBigHeap(array);
        int end = array.length - 1;
        while (end > 0) {
            swap(array, 0, end);
            shiftDown(array, 0, end);
            end--;
        }
    }

    private static void createBigHeap(int[] array) {
        for (int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(array, parent, array.length);
        }
    }

    private static void shiftDown(int[] array, int parent, int len) {
        int child = parent * 2 + 1;
        //至少有左孩子
        while (child < len) {
            if (child + 1 < len && array[child] < array[child + 1]) {
                //有右孩子,且右孩子最大
                child++;
            }
            if (array[child] > array[parent]) {
                swap(array, child, parent);
                parent = child;
                child = 2 * parent + 1;

            } else {
                //child比parent小,不需要调整
                break;
            }
        }

    }

同样的,如果要从大到小排序,就要建立小根堆

练习:
在这里插入图片描述
分析:
在这里插入图片描述

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

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

相关文章

Linux —— 进程地址空间

目录 一&#xff0c;虚拟地址 二&#xff0c;进程地址空间 一&#xff0c;虚拟地址 #include<stdio.h> #include <unistd.h> #include <stdlib.h> int g_val 0; int main() {pid_t id fork();if(id<0) …

聊聊STM32的基本定时器

STM32 的基本定时器&#xff08;Basic Timer&#xff09;是一种简单的定时器模块&#xff0c;用于生成基于时钟频率的定时中断。它可以用于实现各种定时和计时功能&#xff0c;例如延时、频率测量、PWM 生成等。 基本定时器通常由一个 16 位的自由运行计数器和一个预分频器组成…

VMware InstallBuilder Crack

VMware InstallBuilder Crack VMware InstallBuilder是一种开发工具&#xff0c;用于构建桌面和服务器软件的跨平台安装程序。使用InstallBuilder&#xff0c;您可以从单个项目文件和构建环境中快速创建Linux、Windows、Mac OS X、Solaris和其他平台的动态专业安装程序。除了安…

虚拟机安装红帽8/9问题解决方案

虚拟机安装红帽8/9问题解决方案 导入虚拟机文件&#xff0c;开启虚拟机。 会直接进入Redhat Debug 此平台不支持虚拟化的 AMD-V/RVI。 不使用虚拟化的 AMD-V/RVI&#xff0c;是否继续? 解决方案&#xff1a; 首先确保PC是开启VT-x&#xff08;Inter&#xff09;&#xff0…

Docker Compose 实现单机容器集群编排管理

目录 Docker ComposeDocker Compose 三大概念1. Docker Compose 环境安装2. YAML 文件格式及编写注意事项3. Docker Compose配置restart 设置重启策略&#xff0c;no&#xff0c;always&#xff0c;no-failure&#xff0c;unless-stopped 4. Docker Compose 常用命令选项5. Doc…

消息队列(一)-- RabbitMQ入门(2)

发布确认 发布确认原理 生产者将信道设置成 confirm 模式&#xff0c;一旦信道进入 confirm 模式&#xff0c;所有在该信道上面发布的消息都将会被指派一个唯一的 ID&#xff08;从1开始&#xff09;&#xff0c;一旦消息被投递到所有匹配的队列之后&#xff0c;broker 就会发…

蓝桥杯省赛真题——最少刷题数

2022年第13届省赛&#xff0c;蓝桥杯真题。 (本笔记适合初通 Python 的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单…… 地址&…

【前端知识】React 基础巩固(三十三)——Redux的使用详解

React 基础巩固(三十三)——Redux的使用详解 Redux的使用详解 针对React 基础巩固&#xff08;三十二&#xff09;中的案例&#xff0c;我们希望抽取页面中共有的代码&#xff08;例如下方的代码&#xff09;&#xff0c;使用高阶组件统一拦截。 constructor() {super();this.…

Python入门【 for循环和可迭代对象遍历、嵌套循环和综合练习、continue语句、else语句、循环代码优化】(八)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

两个数组的dp问题(2)--动态规划

一)交错字符串: 97. 交错字符串 - 力扣&#xff08;LeetCode&#xff09; 一)确定一个状态标识: 如果我选择s1的一段区间&#xff0c;再进行选择s2得一段区间那么s3这个字符串的长度就已经固定了 预处理:在s1字符串s2字符串和s3字符串前面加上一个虚拟字符&#xff0c;让下标从…

力扣热门100题之最小覆盖子串【困难】【滑动窗口】

题目描述 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 “” 。 注意&#xff1a; 对于 t 中重复字符&#xff0c;我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。…

java项目之人才公寓管理系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的人才公寓管理系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&…

JVM-提问纯享版

一、内存区域 介绍下 Java 内存区域&#xff08;运行时数据区&#xff09;内存分配方式内存分配并发问题对象的访问定位的两种方式&#xff08;句柄和直接指针两种方式&#xff09; 二、垃圾回收 如何判断对象是否死亡&#xff08;两种方法&#xff09;。简单的介绍一下强引…

Web3教程| 什么是地址监控?如何使用地址监控追踪黑客地址?

在当今Web3世界里&#xff0c;保护个人资产安全至关重要。据报道在2023年上半年&#xff0c;Web3领域因黑客攻击事件造成的损失高达4.794亿美元。 此外&#xff0c;10多个公链遭受黑客攻击&#xff0c;其中以太坊链遭受的损失最多&#xff0c;约为2.87亿美元。这些黑客的存在迫…

maven配置下载源

有得时候项目中会配置默认的谷歌作为源下载依赖这样会导致下载很慢&#xff0c;我们可以将谷歌的源更改为国内的阿里源&#xff0c;这样下载就会比较快 查看pom.xml文件 若是在配置时没有指定该依赖的下载源的话&#xff0c;就是默认去谷歌源下载&#xff0c;这时我们在项目po…

KEGG 通路如何找

链接&#xff1a; KEGG: Kyoto Encyclopedia of Genes and Genomes 学习链接&#xff1a; 科研干货&#xff5c;KEGG信号通路数据库轻松上手_哔哩哔哩_bilibili 示例&#xff1a;我要找人的结直肠癌信号通路&#xff1a; 1. 2. 3. 4. over

pytest自动化测试框架,真正做到从0到1由浅入深详细讲解【万字级】

目录 嗨咯铁汁们&#xff0c;很久不见&#xff0c;我还是你们的老朋友凡叔&#xff0c;这里也感谢各位小伙伴的点赞和关注&#xff0c;你们的三连是我最大的动力哈&#xff0c;我也不会辜负各位的期盼&#xff0c;这里呢给大家出了一个pytest自动化测试框架由浅入深详细讲解。 …

mysql(二)SQL语句

目录 一、SQL语句类型 二、数据库操作 三、数据类型 四、创建 五、查看 六、更改 七、增、删、改、查 八、查询数据 一、SQL语句类型 SQL语句类型&#xff1a; DDL DDL&#xff08;Data Definition Language&#xff0c;数据定义语言&#xff09;&#xff1a;用于…

项目经理:我不是不喜欢工作,只是不喜欢开会

大家好&#xff0c;我是老原。 如何高效的开会&#xff0c;我觉得我可太有发言权了&#xff01;作为项目经理&#xff0c;每天就是开会&#xff0c;开会还好&#xff0c;还经常是无效会议。 职场人最讨厌的事情除了加班就是开会了。但很多人认为开会比加班更可恶&#xff0c;…

hackthebox—Sau

文章目录 1、信息收集2、ssrf3、命令执行 1、信息收集 fscan扫描ip发现存在22和55555&#xff0c;但是实际上这个fscan扫描的不全 再试试nmap nmap -sV -sC -sT -v -T4 10.10.11.224 有三个端口&#xff0c;其中80应该是只能内网访问&#xff0c;看来需要借助ssrf了。 2、s…