优先级队列(概念理解/底层模拟/时间复杂度分析)

目录

1.概念理解

2.优先级队列的底层模拟

2.1堆的概念  

2.2优先队列的模拟实现

2.2.1把Heap类定义好

2.2.2初始化堆

2.2.3创建大堆

1.思路

以此二叉树为例:

图文理解:

2.思路转化为代码

2.2.4堆操作之offer(进队列)

1.思路

2.思路转化为代码:

2.2.5堆操作之poll(删除)

思路:

图片演示:

思路转化为代码:

2.2.6 isFuLL和isEmpty

2.3建堆的时间复杂度


1.概念理解

在之前的数据结构学习中,我们都知道队列是一个先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队 列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。

在这种情况下,数据结构应该提供两个最基本的操作一个是每次优先返回优先级高的对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。

2.优先级队列的底层模拟

在JDK1.8中,PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整。

2.1堆的概念  

在我们之前学习中直到,二叉树可以进行链式存储,而堆本质上也是一种二叉树,不过它的存储方式不在是链式存储,而是顺序存储(存储在数组中)

那么一个数组如何表示一颗二叉树呢?

如图,采用一个层序遍历的方式就可以了:

大家可以在仔细观察一下这棵二叉树有什么特点?

没错,在这颗树中:

1.每个节点的左右两个子节点所储存的值都要比双亲节点(父亲节点)储存的值要大

2.这是一颗完全二叉树

实际上,上面这棵树就是一个

堆分为大根堆 以及 小根堆。

        像这种某个节点的值总是不小于其父节点的值,我们称为小根堆

如图:

另外,某一个节点的值总是不大于其父节点的值,我们称之为大根堆

如图:

注意:只要是堆,那么他一定是完全二叉树

因为堆是顺序存储的,如果不是一颗完全二叉树,会出现这种情况:

数组中会有很多空间没有被利用。

而完全二叉树恰好能避免这种情况,实现数据的高效存储

2.2优先队列的模拟实现


想要实现优先队列,实际上就是要创建一个堆,对堆,进行相关的操作。

不过想要用顺序存储这样一个数据结构,好像有亿点难ε=(´ο`*)))唉。

那么它真的很难吗?

如果你已经学过二叉树的基本操作了,那么你可以大胆的对别人说这个堆,它其实亿点也不难ƪ(˘⌣˘)ʃ


优先级队列的底层是堆

堆的底层就是一个数组

下面以大根堆为例,创建一个数组对数组进行操作,进而实现优先级队列

2.2.1把Heap类定义好

class BigHeap {

    int[] elem;
    int useSize;//堆的大小,默认为0

    public BigHeap() {//调用构造方法,数组默认容量是10,不够之后再考虑扩容
        this.elem = new int[10];
    }

    public void init(int[] array) {

    }

    public void creatHeap() {

    }

    public void siftDonw(int parent, int end) {

    }

    public void offer(int val) {

    }

    public void siftUp(int child) {

    }

    public boolean isFull() {
        return false;
    }

    public boolean isEmpty() {
        return false;
    }

}

下面,我们依次实现各种操作方法:

2.2.2初始化堆

    public void init(int[] array) {
        if(array.length>elem.length){//所给的数组大小,比预先堆中的大,要对堆扩容
            Arrays.copyOf(elem,array.length+elem.length);
        }
        for (int i = 0; i <array.length ; i++) {
            elem[i]=array[i];
            useSize++;//使用量,每次加一
        }
    }

2.2.3创建大堆

在此之前,你要掌握一下知识:

在顺序存储的二叉树中:

parent=(child-1)/2----->不论是左孩子,还是右孩子,计算出的都是他们同一个父节点

leftChild=parent*2+1

rightChild=parent*2+1

1.思路

在初始化的时候,我们已经得到了一个完全二叉树。

想要创建大堆,那么这颗完全二叉树的每个子节点的值都不能大于父节点的值。

其实,实现它也很简单。

第一步:

使用一个算法(向下调整算法),当对某一个节点(子树)完成这个算法的时候,这颗子树就变成了一个大根堆

第二步

从这颗初始化的完全二叉树的最后一个父节点【lastParent=((elem.length-1)-1)/2】开始

使用第一步的算法,然后父节点,在跳转到父节点的父节点继续使用该算法,直到遍历到树的root节点为止。

这时,此树就是大根堆了

以此二叉树为例:

图文理解:

从最后一个父节点(度不为0的)也就是9,到根节点(也就是3)

所以,一共要进行次向下调整算法。

第一次向下调整:

解释:

从9节点开始向下比较。

1. 先对13和12进行比较(先对孩子节点比较)

2. 因为13>12,所以13在对9比较。

3. 9<13,所以9和13要交换


由于如果在往下比较,父节点就没有子节点了,所以直接进行第二次向下调整算法

解释:

Dad倒着往上依次遍历,使用向下调整算法,所以这时,Dad指向7位置

与第一次向下调整算法一样

7和12交换,然后直接进行第三次向下调整算法


第三次向下调整算法:

解释:

Dad倒着往上依次遍历,使用向下调整算法,所以这时,Dad指向5位置

5小于最大的孩子节点13,所以13和5进行交换

如图:

此时我们还在进行第三次向下调整,因为当前的5节点的左右孩子都存在因此还要进行比较

在虚线中三个节点,12最大,12和5交换,然后第三次向下调整结束。


第四次向下调整:

解释:

Dad倒着往上依次遍历,使用向下调整算法,所以这时,Dad指向3位置

上图,解同样地,按部就班,13和3交换

上图同样地,3和12交换

上图同样地,3和9交换


然后大根堆就出来了:

2.思路转化为代码
 public void creatHeap() {

        for (int i =((useSize-1)-1)/2; i >=0 ; i--) {
            siftDown(i,useSize-1);//从后往前,全部使用一次向下调整算法
        }
    }


    private void swap(int a,int b){
        int t;
        t=elem[a];
        elem[a]=elem[b];
        elem[b]=t;
    }
    public void siftDown(int parent, int end) {//parent end都是有效下标

        int child=2*parent+1;//默认是左孩子
        while(child<=end){//调整到最后一个子节点,为止
            //先判断是否有右孩子
            if(child+1<=end){//如果有判断谁大,大的当左孩子
                if(elem[child]<elem[child+1]){
                    swap(child,child+1);
                }
            }

            //左孩子在和父节点进行比较
            if(elem[child]>elem[parent]){//如果孩子节点大,那么父子交换位置
                swap(child,parent);
            }else{
                break;//如果父节点已经是最大的就不用调整了,这棵树就是大根堆
                //因为我们会从后往前,把这棵树(数组)一次遍历调整完
            }
            //下面继续往往下面调整
            parent=child;//当前的父亲,变成自己的孩子
            child=parent*2+1;//孩子变成孩子的孩子
        }
    }

2.2.4堆操作之offer(进队列)

1.思路

把要进队的元素放到数组最后一个元素后面:

他进行向上调整一次就OK喽

假设进队元素==13:

向上调整:

和向下调整一样,只是顺序变成了自上而下。

需要进队的元素先与父节点比较,

如果进队元素更大,就交换,然后子节点变成父节点,父节点,变成父节点的父节点,依次比较到根。

一旦父节点比孩子节点都大,那么就退出

调整后的大根堆:

2.思路转化为代码:
 public void siftUp(int child) {
        int parent=(child-1)/2;
        while(parent>=0){
            if(elem[parent]<elem[child]){//孩子节点,大,那么就交换
                swap(elem[parent],elem[child]);
                child=parent;//向上继续比较
                parent=(child-1)/2;//向上继续比较
            }else{//否则调整完毕,直接跳出(因为本身就是一个大根堆)
                break;
            }
        }
    }

2.2.5堆操作之poll(删除)

注意:堆的删除一定是删除堆顶元素!

思路:

1.堆顶元素和最后一个元素进行交换

2.堆的有效个数减少一个

3.对堆顶元素进行一次向下调整

图片演示:

思路转化为代码:

 public int poll(){
        if(isEmpty())return -1;//如果堆是空的,返回一个无效值

        //删除一定是删除除堆顶元素

        //堆顶元素和最后一个元素交换位置,useSize--,然后对堆顶元素进行一次向下调整即可
        swap(0,useSize-1);

        siftDown(0,useSize-1);

        useSize--;

        return elem[useSize];//返回被poll出来的元素值

    }

2.2.6 isFuLL和isEmpty

两个都很简单,也没什么好说的


    public boolean isFull() {
        if(useSize>=elem.length)return true;
        return false;
    }

    public boolean isEmpty() {
        if(useSize==0)return true;
        return false;
    }

2.3建堆的时间复杂度

考虑最坏情况,那么这颗树就是一个满二叉树,并且是一个小根堆(以大根堆为例)。

进而可以简化证明过程:

当到了第h层时:

有2^(n-1)个节点,要向下移动0层

当然,实际上我们上面的程序是从第h层开始执行的,不过这都一样。

它的时间复杂度就可以变成这样:

通过观察我们发现,这其实就是等差乘等比的形式,错位相减法,就能求出项数和。

最终:建堆的时间复杂度为O(N)


相关推荐

  1. 复杂分析-时间复杂和空间复杂

    2024-04-22 21:44:05       50 阅读
  2. 算法的时间复杂和空间复杂-概念

    2024-04-22 21:44:05       23 阅读
  3. 堆排及时间复杂分析

    2024-04-22 21:44:05       49 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-22 21:44:05       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-22 21:44:05       106 阅读
  3. 在Django里面运行非项目文件

    2024-04-22 21:44:05       87 阅读
  4. Python语言-面向对象

    2024-04-22 21:44:05       96 阅读

热门阅读

  1. redis常用5大数据类型

    2024-04-22 21:44:05       36 阅读
  2. 深度学习——Transformer的理解整理

    2024-04-22 21:44:05       39 阅读
  3. XiaodiSec day014 Learn Note 小迪渗透学习笔记

    2024-04-22 21:44:05       36 阅读
  4. 微信小程序

    2024-04-22 21:44:05       37 阅读
  5. notepad++快捷键和宏录制

    2024-04-22 21:44:05       40 阅读
  6. stm32开发三、单片机关键字extern

    2024-04-22 21:44:05       34 阅读
  7. 云原生周刊:CNCF 2023 年度调查报告 | 2024.4.15

    2024-04-22 21:44:05       40 阅读
  8. OpenCV2之简单处理视频

    2024-04-22 21:44:05       37 阅读
  9. Uipath用计划任务启动 bat脚本语句

    2024-04-22 21:44:05       31 阅读
  10. 【C语言】归并排序算法实现

    2024-04-22 21:44:05       36 阅读