当前位置: 首页 > article >正文

【Java数据结构】优先级队列(堆)

优先级队列

        队列是一种先进先出的数据结构,但是有些情况下操作的数据可能带有优先级,一般出队时需要优先级高的先出队,所以就有了优先级队列的概念。优先级队列提供两个基本操作,一个是返回最高优先级对象,一个是添加新的对象。

堆 

        而且这个优先级队列的底层运用了堆的知识,优先级队列是顺序存储的二叉树,堆则是优先级队列调整为完全二叉树。堆的存储方式是在一个一维数组里,它又分大堆(堆中根结点的值总是大于左右结点)和小堆(堆中根结点的值总是小于左右结点)。堆是通过层序按顺序存储的(利用数组和完全二叉树可以充分利用数组内空间避免浪费)

例:假如现在有一个集合{27,15,19,18,28,34,65,49,25,37}的数据,怎么将它创建成堆?

        首先我们先将其集合转成堆的结构(完全二叉树),将其转成后发现并不符合堆的性质,所以我们还需要进行调整,调整成大堆或小堆。不管是小堆还是大堆都要从根结点开始向下调整,所以这个方法就是向下调整,下面以大堆为例。

从最后一个结点开始,先找到最后一个结点的父结点,然后对比父结点的左右结点(如果没有右结点直接比较左结点),将孩子结点指向值大的那个,然后就对比父结点和孩子结点,如果父结点小将两个节点交换即可。这个父结点逐减减1直至结点不再存在(即小于0),但是要注意的就是并不是每个父结点都有孩子结点,所以找孩子结点时需要限制条件(孩子结点要小于最后一个结点)。

public class priorityQueue {
    private int[] elem;
    private int usedsize;
    public priorityQueue(){
        elem = new int[11];
    }
    public void init(int[] arr){
        for (int i = 0; i < arr.length; i++){
            this.elem[i] = arr[i];
            usedsize++;
        }
    }
    public void maxpriority(){
        for (int p = (usedsize - 1)/2; p >= 0; p--){
            shiftdown(p, usedsize);
        }
    }
//向下调整
    private void shiftdown(int parent, int usedsize){
        int child = (2*parent)+1;
        while (child < usedsize){
            if (child+1 < usedsize && elem[child] < elem[child+1]){
                child++;//将孩子结点指向值大的那个结点
            }
            if (elem[parent] < elem[child]){
                swap(parent, child);
                parent = child;
                child = parent*2+1;
            }else{
                break;
            }

        }

    }
//交换父结点和孩子结点
    private void swap(int i, int j){
        int t = elem[i];
        elem[i] = elem[j];
        elem[j] = t;
    }
}

现在我们来计算一下它的复杂度,它的复杂度 = 该层结点数*该层调整高度的总和,再通过一系列操作就能知道向下调整的复杂度,所以采用向下调整建堆的时候复杂度为O(n)。

 堆的插入和删除

插入:插入是从一个原先就是堆中插入,方法是先将要插入的结点放入二叉树中最末尾,然后再对整棵二叉树进行调整,这个过程称向上调整。

        插入结点前也需要判断数组的容量是否可以添加一个元素(即判断数组是否满),然后将结点添加到最后再调整,调整从最后一个结点开始,向上调整直至整棵树符合堆的性质(大堆或小堆,全篇以大堆为头),需要从最后一个结点找到其父结点比较大小,插入的结点小的话直接终止比较即可,如果比父结点大则需要将两者交换,然后将孩子结点变成父结点,再找孩子结点的父结点,一直循环,直至孩子结点等于0就停止循环。

    //交换两个结点
    private void swap(int i, int j){
        int t = elem[i];
        elem[i] = elem[j];
        elem[j] = t;
    }
    //向上调整
    public void offer(int val){
        if (isFull()){
            this.elem = Arrays.copyOf(elem, 2*elem.length);
        }
        elem[usedsize] = val;
        shifUp(usedsize);
    }
    private boolean isFull(){
        if (usedsize == elem.length){
            return true;
        }
        return false;
    }
    private void shifUp(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;
            }
        }
    }

删除:是将堆中的堆顶元素删除(即完全二叉树中的根结点),思路是先将根结点与最后一个结点交换,然后将计数器减1即可。

public int poll(){
        int temp = elem[0];
        swap(0, usedsize-1);
        shiftdown(0, --usedsize);
        return temp;
    }

 下面有一些关于堆相关的题目

例1:

做这题我们需要知道堆的性质(大堆和小堆的概念),看题目可以知道这个是大堆为准,将数组构建成一个完全二叉树,对比每一棵子树是否满足根结点大于左右结点即可,答案选 A。

例2:

根据堆的删除需要将根结点与最后一个结点交换,计数器减1,然后再调整堆。调整需要对左右结点进行比较,直至堆符合堆的性质,答案选C。

常用接口

PriorityQueue的特性 

PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的。

PriorityQueue默认是小堆;放入的元素必须能够比较大小,如果放入的对象是不能比较的就会抛出异常;也不能插入null对象,不然也会抛异常;没有容量限制可以插入任意个元素(内部会自动扩容);插入和删除的时间复杂度为O(logN);PriorityQueue底层使用了堆数据结构。

PriorityQueue常用接口

构造方法有无参构造、指定容量构造、采用集合构造。默认情况下PriorityQueue队列的是小堆,如果要创建一个大堆需要提供比较器。

PriorityQueue q = new PriorityQueue();

PriorityQueue接口下的一些方法;

例:top-K问题:最大或最小的前k个数据。比如:世界前500强公司

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

        如果是按照我们通用思维逻辑来说,可能都是将这些数据从大到小或从小到大排序,如果面临的是少个数据这个排序的方法可以使用,但面临很多数据时很明显效率就低了。我们现在换一种思考想这个问题,如果我们先将前k个数据构成一个大堆,然后再将数组剩下的数依次进行比较,如果比根结点小那就先删除根结点再插入数组,比根结点大说明并不是前k个最小的就跳过这次循环即可。

class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if (k <= 0){
            return ret;
        }
        PriorityQueue<Integer> p = new PriorityQueue(new IntCmp());
        for(int i = 0; i < k; i++){
            p.offer(arr[i]);
        }
        for(int i = k; i < arr.length; i++){
            int top = p.peek();
            if (arr[i] <  top){
                p.poll();
                p.offer(arr[i]);
            }
        }
        for (int i = 0; i < k; i++){
            ret[i] = p.poll();
        }
        return ret;
    }
}

 关于优先级队列的相关内容都在这啦,大家可以参考一下呢~


http://www.kler.cn/a/511625.html

相关文章:

  • KubeSphere 与 Pig 微服务平台的整合与优化:全流程容器化部署实践
  • ChatGPT 写作系列
  • 汇编与逆向(一)-汇编工具简介
  • 【24】Word:小郑-准考证❗
  • Windows 通过 openssh 连接 Ubuntu 24.04 LTS
  • leetcode300.最长递增子序列
  • css‘s hover VS mobile
  • UnderTow服务器
  • 第10章:Python TDD优化货币类方法与引入工厂方法
  • 【学习笔记15】如何在非root服务器中,安装属于自己的redis
  • rocketmq dashboard 安装
  • w-form-select.vue(自定义下拉框组件)
  • 1.写在前面
  • 【无标题】Cloudlog 电台日志系统 request_form SQL注入漏洞复现
  • Linux自学指南(学习路线大纲)
  • 【机器学习:三十三(二)、支持向量机(SVM)的核函数:概念、类型与应用】
  • PyTorch使用教程(8)-一文了解torchvision
  • 信息安全【在Ubuntu中安装nginx、MySQL和部署PHP】
  • vmware17.5 - 解决ubuntu长按按键会导致图形界面卡死的情况
  • ThinkPhp项目解决静态资源请求的跨域问题的解决思路