优先级队列(堆)的概念和基本操作

梦想不抛弃苦心追求的人,只要不停止追求,你们会沐浴在梦想的光辉之中。再美好的梦想与目标,再完美的计划和方案,如果不能尽快在行动中落实,最终只能是纸上谈兵,空想一番。只要瞄准了大方向,坚持不懈地做下去,才能够扫除挡在梦想前面的障碍,实现美好的人生蓝图。优先级队列(堆)的概念和基本操作,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

在这里插入图片描述

1. 堆逻辑上是一棵完全二叉树
2. 堆物理上是保存在数组中
3. 满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆
4. 满足任意结点的值都小于其子树中结点的值,叫做小堆,或者小根堆,或者最小堆
5. 堆的基本作用是,快速找集合中的最值

存储方式

二叉树在代码中的表示方式:
孩子兄弟表示法(N叉树)
左右孩子表示法(二叉树)
使用数组表示二叉树:
使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中,一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费,这种方式的主要用法就是堆的表示。

在这里插入图片描述

下标关系

在数组中存储的下标关系:

在这里插入图片描述

A 下标:0 左子树下标:1 右子树下标:2
B 下标:1 左子树下标:3 右子树下标:4
C 下标:2 左子树下标:5 右子树下标:6
D 下标:3 左子树下标:7 右子树下标:8
.。。。

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

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

操作-向下调整(大堆)

给定一个数组,当前这个数组不满足堆的特性,经过操作使其变成一个堆。
**前提:**要求当前这个树中,除了根节点之外,其他节点都符合堆的要求
在这里插入图片描述
除了根节点,其它节点已经符合大堆结构,从根结点出发进行向下调整。
在这里插入图片描述

1、从根节点出发,先找到左右子树根节点中较大值
2、把找到的较大的值和原根节点进行交换
3、如果没有完成调整,从此时的当前节点出发,重复1、2步骤

在这里插入图片描述

  public static void shiftDown(int[] array, int size,int index){
        int parent = index;
        //根据父节点下标,先找到左子树下标
        int child = 2*parent+1;

        while (child < size){
            //再去找下右子树对应的节点
            if(child + 1 < size && array[child + 1] > array[child]){//child合法并且如果右子树大于根
                child = child + 1;
            }
            //child经过上面的条件,指向左右子树中较大的一个
            //拿刚刚child位置的元素和parent位置进行对比,看是否符合大堆要求
            //如果不符合大堆要求(child位置的元素比parent位置的元素大)交换child和parent位置的元素
            if(array[child] > array[parent]){
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else{
                //当前child和parent的关系符合大堆要求,调整完毕
                break;
            }
            //下次循环之前,需要先更新parent和child
            parent = child;
            child = 2*parent + 1;

        }
    }

操作-向上调整(大堆)

和向下调整基本类似
在这里插入图片描述

private void shiftUp(int[]array,int size,int index){
        int child = index;
        int parent = (child - 1)/2;
        while (child > 0){
            if(array[parent] < array[child]){
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else {
                break;
            }
            child = parent;
            parent = (child -1)/2;
        }

建堆(大堆)

基于向下调整的方式建堆,必须从后往前遍历
基于向上调整的方式建堆,必须从前往后遍历

从后往前遍历,从最后一个节点的父节点出发(最后一个非叶子节点)

在这里插入图片描述

   public static void creatHeap(int[] array,int size) {
        //从后向前遍历,从最后一个非叶子节点出发,依次进行向下调整
        //size-1最后一个节点,再-1/2是找到最后一个节点的父节点
        for(int i = (size - 1 -1)/2; i > 0 ;i--){
            shiftDown(array,size,i);

        }
    }

优先队列(大堆)

每次出队列的元素都是优先级最高的元素
优先级队列的实现方式有很多,但最常见的是使用堆来构建。
入队列的时候,就把元素插入到数组末尾,然后向上调整。
进行出队列的时候,就把堆顶的元素删除,并且进行向下调整

时间复杂度

入队列 =》O(logN)
出队列 =》O(logN)
取队首元素 O(1)
建堆=》O(N)

入队列:

1. 首先按尾插方式放入数组
2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束
3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤
4. 直到根结点

在这里插入图片描述

  public void offer(int x){
        array[size] = x;
        size++;
        shiftUp(array,size,size-1);
    }
    private void shiftUp(int[]array,int size,int index){
        int child = index;
        int parent = (child - 1)/2;
        while (child > 0){
            if(array[parent] < array[child]){
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else {
                break;
            }
            child = parent;
            parent = (child -1)/2;
        }
    }

出队列

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向下调整方式重新调整成堆

1、取最后一个元素复制到根节点
2、尾删最后一个
3、进行向下调整

在这里插入图片描述

 public Integer poll(){
        if(size <= 0){
            return null;
        }
        int ret = array[0];
        array[0] = array[size-1];
        size--;
        shiftDown(array,size,0);
        return ret;
    }
    private void shiftDown(int[] array,int size,int index){
        int parent = index;
        int child = 2 * parent + 1;
        while (child < size){
            if(child + 1 < size && array[child+1] > array[child]){
                child = child+1;
            }
            if(array[parent] < array[child]){
                int tmp = array[child];
                array[parent] = array[child];
                array[child] = tmp;
            }else {
                break;
            }
            parent = child;
            child = 2 * parent + 1;
        }
    }

取队首元素

返回堆顶元素即可

 public Integer peek(){
        if(size == 0){
            return null;
        }
        return array[0];
    }

判空

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

总代码:

public class MyPriority {
    private int[] array = new int[100];
    private int size = 0;//[0,size)表示有效元素区间

    public void offer(int x){
    //1、先把x放到数组元素的末尾
        array[size] = x;
        size++;
        //2、把x进行向上调整
        //第一个参数表示数组,第二个为有效元素的个数,第三个参数表示从哪个位置进行向上调整
        shifUp(array,size,size-1);
    }

    private void shifUp(int[] array, int size, int index) {
        int child  = index;
        //根据父节点下标,先找到左子树下标
        int parent = (child-1)/2;
        //如果child=0,说明child已经是根节点,根节点无父节点

        while (child > 0){
            //比较当前child和parent之间的大小关系看看是否符合大堆
            //再去找下右子树对应的节点
            if(array[parent] < array[child]){//child合法并且如果右子树大于根
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            } else{
                //当前child和parent的关系符合大堆要求,调整完毕
                break;
            }
            //下次循环之前,需要先更新parent和child
            child = parent;
            parent = (child-1) / 2 ;
        }
    }
    public Integer poll(){
    if(size <= 0){
        return null;
    }
    int ret = array[0];
    //如何删除队首元素(根节点)
        //1、把最后一个元素的值填入到0号元素
        array[0] = array[size-1];
        //2、删除最后一个元素
        size--;
        //3、从0下标开始向下调整
        shifDown(array,size,0);
        return ret;

    }
    private void shifDown(int[] array,int size,int index){
        int parent = index;
        //根据父节点下标,先找到左子树下标
        int child = 2 * parent + 1;

        while (child < size){
            //再去找下右子树对应的节点
            if(child + 1 < size && array[child + 1] > array[child]){//child合法并且如果右子树大于根
                child = child + 1;
            }
            //child经过上面的条件,指向左右子树中较大的一个
            //拿刚刚child位置的元素和parent位置进行对比,看是否符合大堆要求
            //如果不符合大堆要求(child位置的元素比parent位置的元素大)交换child和parent位置的元素
            if(array[child] > array[parent]){
                int tmp = array[child];
                array[child] = array[parent];
                array[parent] = tmp;
            }else{
                //当前child和parent的关系符合大堆要求,调整完毕
                break;
            }
            //下次循环之前,需要先更新parent和child
            parent = child;
            child = 2 * parent + 1;

        }
    }

    public Integer peek(){
    if(size == 0){
        return null;
    }
    return array[0];
    }
    public boolean isEmpty(){
        return size == 0;
    }

    public static void main(String[] args) {
        MyPriority myPriorityQueue = new MyPriority();
        myPriorityQueue.offer(5);
        myPriorityQueue.offer(6);
        myPriorityQueue.offer(9);
        myPriorityQueue.offer(1);
        myPriorityQueue.offer(3);
        myPriorityQueue.offer(0);
        myPriorityQueue.offer(7);
        while (!myPriorityQueue.isEmpty()){
            Integer cur = myPriorityQueue.poll();
            System.out.print(cur+" ");
        }
    }
}

结果:

9 7 6 5 3 1 0 

优先队列(小堆)

只要把上述的大堆代码稍作修改即可
在这里插入图片描述
在这里插入图片描述

topK问题

从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题。
就是在一个数组中把较大的前K个数输出

把N个元素建个堆,循环poll k次
如果把N个元素建堆,然后出队列N次就得到一个有序序列

Java库中的优先队列

在这里插入图片描述

标准库中的优先队列默认的是小堆,定义了“值越小,优先级越高”

import java.util.PriorityQueue;
public class Test {
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        queue.offer(9);
        queue.offer(5);
        queue.offer(2);
        queue.offer(0);
        queue.offer(3);
        queue.offer(4);
        queue.offer(7);

        while (!queue.isEmpty()){
            Integer cur = queue.poll();
            System.out.print(cur+" ");
        }
    }
}

可以通过手动定义一个比较器对象改变建堆的方式,借助比较器对象。
在这里插入图片描述
匿名内部类:
在这里插入图片描述
java8中lambda表达式
在这里插入图片描述

import java.util.Comparator;
import java.util.PriorityQueue;
public class Test {
    static class MyComp implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {
            //如果认为01优先级比o2高,先出o1 compare返回<0的整数
            //如果认为02优先级比o1高,先出o2 compare返回>0的整数
            //如果一样,返回0
            return o2 - o1;
        }
    }
    public static void main(String[] args) {
        PriorityQueue<Integer> queue = new PriorityQueue<>(new MyComp());
        queue.offer(9);
        queue.offer(5);
        queue.offer(2);
        queue.offer(0);
        queue.offer(3);
        queue.offer(4);
        queue.offer(7);

        while (!queue.isEmpty()){
            Integer cur = queue.poll();
            System.out.print(cur+" ");
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/152996.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!