首页 > 编程语言 >Java-数据结构-优先级队列(堆)-(一) (;´д`)ゞ

Java-数据结构-优先级队列(堆)-(一) (;´д`)ゞ

时间:2024-09-18 22:50:38浏览次数:9  
标签:优先级 parent int usedSize elem child Java 数据结构 我们

文本目录:

❄️一、优先级队列:

          ➷ 1、概念:

❄️二、优先级队列的模拟实现:

          ➷ 1、堆的概念:

          ➷ 2、堆的性质:

           ➷ 3、堆的创建:

  ▶ 向下调整:

            ➷ 4、堆的插入和删除:

       ▶ 堆的插入:

 ☞ 思路:

 ☞ 代码:

        ▶ 堆的删除:

 ☞ 思路:

 ☞ 代码:

            ➷ 5、堆的判空和判满:

            ➷ 6、堆的对顶数据:

             ➷ 7、堆的总代码:

❄️总结:


❄️一、优先级队列:

          ➷ 1、概念:

     我们在前面是介绍过队列的,队列是一种先进先出的数据结构,但是在有些情况下呢,我们操作的数据有优先级,这时候,在我们出队列的时候呢,可能需要先出优先级高的先出队列,那么在这种情况下呢,我们的队列就是有些不合适了,比如呢,我们在 玩游戏的时候呢,来个电话,是不是手机先处理电话,之后再处理游戏去。

      在这种情况下,数据结构提供了两种最基本的操作:一个事返回优先级最高的对象,另一个是添加新的对象。这种操作呢就是 优先级队列——Priority Queue


❄️二、优先级队列的模拟实现:

     在 JDK1.8 中呢,我们的 Priority Queue 的底层使用了堆的数据结构,而对于 堆呢就是在 完全二叉树 的基础上进行一些调整的。

          ➷ 1、堆的概念:

   如果有一个关键码的集合 K = {K0,K1,K2,.......,Kn - 1},把它的所有数据都按照 完全二叉树的顺序存储方式存储 在一个一维数组中,并且满足:

     Ki <= K2i + 1 且 Ki <= K2i + 2 (Ki >= K2i + 1 且 Ki >= K2i + 2) i = 0,1,2,3,4,5.......,

   则称为 小堆(大堆)。

   在这里我们 将根节点最大的堆称为 最大堆 或者 大根堆

                      将根节点最小的堆称为 最小堆 或者 小根堆


          ➷ 2、堆的性质:

•  堆中某个节点的值总是 不大于  或者 不小于 其父节点的值。

•  堆是一颗完全二叉树。

我们来看看 大根堆 和 小根堆 的逻辑结构和存储结构:

     我们的存储方式是采用 层序存储 的 规则来进行存储到数组中。这样就非常的高效了

     我们要注意如果不是 完全二叉树 的情况下呢,我们的不能使用 层序遍历的存储规则,这样会浪费空间,所以如果不是完全二叉树不能这样做。


           ➷ 3、堆的创建:

     我们在创建堆之前呢,我们先来做一些准备工作,我们要注意我们的堆的底层使用的是数组进行实现的。

 我们将这些数据变成 大根堆 或者 小根堆 但是呢,我们要如何才能创建 大根堆 或者 小根堆 呢?

在创建 大根堆 或者 小根堆 之前呢,我们先把这个数组的值呢变成 完全二叉树 来看看:

我们红色的字体就是我们的数组的下标。这个就是我们这组数据的 完全二叉树 的逻辑结构。

 那么我们要如何创建堆呢?这里呢我们要了解到一个知识点——向下调整


  ▶ 向下调整:

      我们这里是 从最后一颗子树进行调整,找到最后一颗子树的根节点,再比较这个根节点的左右节点谁大,我们把根节点和这个最大值进行调整,这样循环调整每一颗子树。这样就可以调整成大根堆。

这里呢,我们直接看图来理解这个问题:

这就是我们的大根堆的调整方法从 完全二叉树 调整。 

这里我们需要一些必须要求的东西:

1、首先要找到最后一颗子树的位置,也就是最后一颗子树的根节点。

     这里我们使用一个公式:(数组的长度 - 1 - 1)/ 2  就是我们的最后一颗子树的根节点。我们使用 parent 标记这个节点。

2、我们如何找到我们的下一颗子树的根节点位置?

      这个就比较简单了,我们直接 parent-- 就可以了。直至我们的 parent 下标 >= 0 即可比如:

3、我们要知道我们的子树的 左子树 ,为什么是左子树呢?因为是由 完全二叉树 调整而来,如果        没有左子树就没有右子树,所以只需要找到左子树即可

      那么怎样去找呢?这里也有公式:child = parent * 2 + 1 ,就是我们左子树。但是这个呢我们还要找其左右子树最大的值,不是直接对child进行交换的并且要注意我们的 child 要 小于 有效的数组长度

 4、我们怎样知道我们的调整结束了?

      这里我们不能调整一次就结束,这里的结束条件就是,当我们的 左子树这个节点的下标比有效数组长度长的时候就是结束的时候了

      每次我们要对 child 和 parent 进行调整。parent = child     child = parent * 2 + 1


我们来演示一变看看如何进行的:

这就是我们的 向下调整  成 大根堆 的操作。来看看这个 向下调整 的代码:

/**
     *
     * @param parent 每颗子树调整时候的起始位置
     * @param usedSize 用来判断 每颗子树什么时候结束的
     */
    private void shiftDown(int parent,int usedSize) {
        int child = parent * 2 + 1;//parent根节点的左子树的节点

        while (child < usedSize) {

            //判断有没有右子树,如果有就把 child 设置为最大的值的下标
            if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
                //右子树比左子树大把 child + 1,就是右子树
                child += 1;
            }

            if (elem[parent] < elem[child]) {
                //进行交换
                swap(elem,child,parent);
                //调整完,把 parent 和 child 进行调整位置
                parent = child;
                child = parent * 2 + 1;
            }else {
                //这是 parent下标的值大于child,跳出
                break;
            }
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

     这个呢就是我们的 向下调整 的 创建大根堆,对于我们的要是 创建 小根堆 的话就是把其小的数值进行调整就可以了,就是反过来进行调整


     我们这个时候来看看对于堆的创建,了解到 向下调整 之后呢,就是非常的容易了,我们呢就是把每一个 parent 进行 向下调整,这里就用到循环遍历就可以了。

     我们来看代码:

//堆的创建
    public void createHeap() {
        for (int parent = (this.usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

Ok啊,这样我们的 大根堆的创建 就创建完成了。


            ➷ 4、堆的插入和删除:

       ▶ 堆的插入:

 ☞ 思路:

      对于我们的堆的插入的操做呢,我们的插入一定是在最后一个位置插入的,这个位置,我们一定是知道的,就是我们的 usedSize 这个下标的位置插入数据。我们就可以根据这个位置求出其插入的值的根节点的位置。

       我们来看图来理解:

    这个 90 就是我们新插入的值,我们再对其进行 向上调整,我们是知道插入值的下标,就可以计算到根节点的下标,就可以进行 向上调整了 。 

再对其进行向上调整就可以了。 

 

这里我们也要注意当我们的数组使用满了后,我们就需要扩容了。

这个就是插入的操作了。我们来看看代码是如何编写的: 

 ☞ 代码:
//堆的插入
    public void offer(int val) {

        if (usedSize == elem.length) {
            //满了。2倍扩容
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }

        //插入操作
        this.elem[usedSize] = val;
        //这个时候 usedSize 位置就是我们的子树的坐标位置
        shiftUp(usedSize);
        this.usedSize++;
    }

    private void shiftUp(int child) {

        int parent = (child - 1) / 2;

        while (parent >= 0) {
            //进行向上调整
            if (this.elem[parent] < this.elem[child]) {
                //交换
                swap(elem,child,parent);
                //parent 和 child 调整位置
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }

OK,这个呢就是我们的插入操作的代码了之后我们来看看删除操作是如何做到的。 


        ▶ 堆的删除:

 ☞ 思路:

    这个删除操作还是很简单的,我们需要删除优先级高的数据,所以我们删除的就是 0 下标的值

我们呢有三个步骤:

1、把堆的 0 下标的数据 和 usedSize - 1 这个下标的数据进行交换,就是第一个数据和最后一个         数据进行交换。

2、我们把有效数组长度减一。

3、我们就是 0 这个下标不是大根堆的结构,所以我们对 0 这个下标进行 向下调整 操作。

这个呢就是我们删除的思路了,我们接下来看看其代码: 

 ☞ 代码:
//堆的删除操作
    public int poll() {
        //删除优先级最高的就是0下标的值,有三个步骤:
        //1、把堆的第一个数据和最后一个数据进行交换
        //2、把有效数据长度进行减1
        //3、把 0 下标的值进行向下调整
        if (usedSize == 0) {
            //堆为空,结束
            return -1;
        }

        int val = elem[0];
        //1、
        swap(elem,0,usedSize - 1);
        //2、
        usedSize--;
        //3、
        shiftDown(0,usedSize);

        return val;
    }

            ➷ 5、堆的判空和判满:

这两个操作就非常的简单了,我们直接来看代码:

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

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

            ➷ 6、堆的对顶数据:

这个同样非常的简单,我们直接返回下标为 0 的值就可以了。

    //返回堆顶的数据
    public int peek() {
        if (isEmpty()) {
            return -1;
        }
        
        return this.elem[0];
    }

这样呢我们所有的基本操作就都完成了,我们来看一下总代码: 


             ➷ 7、堆的总代码:

import java.util.Arrays;

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

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

    public void initElem(int[] array) {
        //把elem里的值设置为我们输入的值
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
        }
    }

    

    /**
     * 建堆的时间复杂度为O(N)。
     * 大根堆
     * @param parent 每颗子树调整时候的起始位置
     * @param usedSize 用来判断 每颗子树什么时候结束的
     */
    private void shiftDown(int parent,int usedSize) {
        int child = parent * 2 + 1;//parent根节点的左子树的节点

        while (child < usedSize) {

            //判断有没有右子树,如果有就把 child 设置为最大的值的下标
            if (child + 1 < usedSize && elem[child + 1] > elem[child]) {
                //右子树比左子树大把 child + 1,就是右子树
                child += 1;
            }

            if (elem[parent] < elem[child]) {
                //进行交换
                swap(elem,child,parent);
                //调整完,把 parent 和 child 进行调整位置
                parent = child;
                child = parent * 2 + 1;
            }else {
                //这是 parent下标的值大于child,跳出
                break;
            }
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

    //大根堆的创建
    public void createHeap() {
        for (int parent = (this.usedSize - 1 - 1) / 2; parent >= 0 ; parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

    //堆的插入
    public void offer(int val) {
        if (isFull()) {
            //满了。2倍扩容
            this.elem = Arrays.copyOf(this.elem, 2 * this.elem.length);
        }

        //插入操作
        this.elem[usedSize] = val;
        //这个时候 usedSize 位置就是我们的子树的坐标位置
        shiftUp(usedSize);
        this.usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child - 1) / 2;
        while (parent >= 0) {
            //进行向上调整
            if (this.elem[parent] < this.elem[child]) {
                //交换
                swap(elem,child,parent);
                //parent 和 child 调整位置
                child = parent;
                parent = (child - 1) / 2;
            }
            else {
                break;
            }
        }
    }

    //堆的删除操作
    public int poll() {
        //删除优先级最高的就是0下标的值,有三个步骤:
        //1、把堆的第一个数据和最后一个数据进行交换
        //2、把有效数据长度进行减1
        //3、把 0 下标的值进行向下调整
        if (isEmpty()) {
            //堆为空,结束
            return -1;
        }

        int val = elem[0];
        //1、
        swap(elem,0,usedSize - 1);
        //2、
        usedSize--;
        //3、
        shiftDown(0,usedSize);

        return val;
    }

    //返回堆顶的数据
    public int peek() {
        if (isEmpty()) {
            return -1;
        }

        return this.elem[0];
    }

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

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

❄️总结:

     OK,这篇博客呢就到这里就结束了,这篇博客我们介绍的是 优先级队列 的底层操作代码,下一篇博客我们来看看 Java 自带的 优先级队列吧~~并且还有一些关于我们的优先级队列的习题,敬请期待吧!!!拜拜·~~~

标签:优先级,parent,int,usedSize,elem,child,Java,数据结构,我们
From: https://blog.csdn.net/2303_80388948/article/details/142319449

相关文章

  • Java大小端转换 Java大端转小端 Java小端转大端
    Java大小端转换Java大端转小端Java小端转大端写在前面Java字节转大端整数Java整数转大端字节Java数组转小端整数Java大小端转换提醒写在前面一段内存地址的两边分为高位和低位,就像鸡蛋的两边,大的一端称为大端,小的一段称为小端。在内存地址的高位存储内存的低地址......
  • java-基于springboot实现家教管理系统
    摘要传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装家教管理系统软件来发挥其高效地信息处理的作用,可以规范信息管理流程,让管理工作可以系统化和程序化,同时,家教管理系统的有效运......
  • JavaScript语法入门七 数据类型
     BigInt类型在JavaScript中,“number”类型无法代表大于 253(或小于 -253)的整数。此时可以使用BigInt类型。使用方法:在数字的尾部附加一个n。constbigInttest=12345678901234567890123456789012345678901121345526789n; String类型js中只有String类型没有char类型。定义时......
  • 【面试经验】2024年9月滴滴后端笔试 java
    比较简单,两题编程。选择题好像是20题,有部分不确定,有C++的几题。题目记不清了,凭印象写一下。编程题第一题充电第一题:n个玩具,m电量,尽可能让一个大的区间内的玩具的电量充满。输出充满电的玩具个数。双指针+滑动窗口。importjava.util.Scanner;publicclassMa......
  • 1. 如何在Java中连接MySQL数据库?请解释使用JDBC连接的步骤。
    要在Java中连接MySQL数据库,通常使用JDBC(JavaDatabaseConnectivity)API。这是一个用于执行SQL语句的JavaAPI,可以用来访问关系型数据库。下面是使用JDBC连接MySQL数据库的详细步骤:1.添加MySQLJDBC驱动首先,需要确保项目中包含MySQL的JDBC驱动程序。这个驱动程序通常是一个......
  • java list<Map<String,Object>> 转成对应的对象
    将List<Map<String,Object>>转换为对应的对象可以通过反射或手动映射来实现。以下是一个示例,演示如何使用手动映射的方式将List<Map<String,Object>>转换为对象列表。示例代码假设我们有一个简单的对象类User:publicclassUser{privateStringname;privateint......
  • 前端——JavaScript练习 做一个todoList
    用前端制作一个todoList的表格,实现更新、删除、修改等功能。涉及几个知识点:设置最小高度(宽度):.container{min-width:350px;/*最小宽度最小不会小于210px*/} 去掉外轮廓outline:none;去除字符串两端的空白字符(包括空格、制表符、......
  • java代码-编译-打包-执行_云原生时代笔记总结
    楔子:可能201几年开发的时候,有个ide,加上打包插件,就可以开发了,测试的时候war包扔到服务器容器里就可以跑起来了。(而且没有做过批处理的小伙伴对jar包执行可能不甚熟悉,只见过web容器的目录结构和lib中的jar包。)但是2015年之后应该是全面进入,云原生时代。大量操作需要linux命令行......
  • java-----Stream流
    什么是Stream?Stream将要处理的元素集合看作一种流,在流的过程中,借助StreamAPI对流中的元素进行操作,比如:筛选、排序、聚合等Stream流的作用:结合了Lambda表达式,简化集合、数组的操作Stream流的使用步骤:    ①先得到一条Stream流(流水线),并把数据放上去  ......
  • [Java基础]Stream流
    当我第一次阅读Java8中的StreamAPI时,说实话,我非常困惑,因为它的名字听起来与JavaI0框架中的InputStream和OutputStream非常类似。但是实际上,它们完全是不同的东西。Java8Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合进行链状流式的操作。本文......