优先级队列------堆(Heap)

一、三种堆的区别:
1、 优先级队列(堆):是一种特殊的数据结构,本质上是一个完全二叉树
2、Java中的"堆":JVM持有的内存中的一个内存区域
3、操作系统中的"堆":每个进程也持有一定的内存空间,在这上面也有一个内存区域叫做堆

- 优先级队列:
优先级队列也是一个队列,但不是单纯的先进先出,而是把优先级最高的先出去,该队列内部的结构就是’堆’

- 常用接口

1、PriorityQueue
1)单纯的整形优先级队列入队和出队—按照数字大小顺序来出队

import java.util.PriorityQueue;

public class Test {
    public static void main(String[] args) {
        //创建一个优先级队列
        PriorityQueue<Integer> queue=new PriorityQueue<>();
        //创建一个数组
        int[] arr={4,6,1,2};
        for(int x:arr){
            //入队列
            queue.offer(x);
        }
        while(!queue.isEmpty()){
            //出队
            System.out.println(queue.poll());
        }
    }
}

2)对象在一个类中的比较

例如:创建一个BoyFriend类


import java.util.PriorityQueue;


class BoyFriend{
        public String name;
        public int age;
        public int money;
        public int face;

        public BoyFriend(String name, int age, int money, int face) {
            this.name = name;
            this.age = age;
            this.money = money;
            this.face = face;
        }
    }
    public class Test {

        public static void main(String[] args) {
            BoyFriend[] arr={
                    new BoyFriend("张三",20,10,100),
                    new BoyFriend("李四",40,80,50),
                    new BoyFriend("王五",50,100,10),
            };
            //创建一个优先级队列
            PriorityQueue<BoyFriend> queue=new PriorityQueue<>();
            for(BoyFriend x:arr){
                queue.offer(x);
            }
            while(!queue.isEmpty()){
                BoyFriend cur=queue.poll();
                System.out.println(cur.name);
            }
        }
    }


**注意:**此时会抛出一个异常
在这里插入图片描述------向下转型异常

在这里插入图片描述

  • **Comparable<>**是标准库内置的一个“接口”(带泛型参数,用来指定两个对象之间的大小关系,用来定义比较规则),里面有一个抽象方法*compare To(Object other),*通过这个方法来指定对象自身和另外一个对象之间的大小关系(即比较this和other之间的大小关系)
    this this >other,返回一个>0的值
    this=other,返回一个=0的值

修改结果:

1)利用Comparable接口—哪个类需要比较,就让这个类实现该接口(只能实现只有一种比较规则的情况—即只能用一次(eg:money))

class BoyFriend implements Comparable<BoyFriend>{
        public String name;
        public int age;
        public int money;
        public int face;

        public BoyFriend(String name, int age, int money, int face) {
            this.name = name;
            this.age = age;
            this.money = money;
            this.face = face;
        }

        //实现Comparable里的抽象方法
    @Override
    public int compareTo(BoyFriend o) {
        //将当前对象和o进行比较
        //注意:此处的减数和被减数不用刻意去记,可根据结果进行调整
        //根据钱的多少来比较
        return o.money-this.money;
    }
}

2)利用 Comparator接口—定义一个新的类,实现该接口,里面的compare方法的参数是两个对应要比较的类(更加适用于指定了多种比较规则的情况—可创建多个Comparator,用到哪个的时候就使用哪个)

//接口里的参数是要被比较的类
class BoyFriendComparator implements Comparator<BoyFriend>{
    @Override
    public int compare(BoyFriend o1, BoyFriend o2) {
        return o1.money-o2.money;
    }
}

二、 优先级队列的应用

  • 用来排序(堆排序)
  • 用来解决topk问题(找出前k个最大或者最小元素)

1、堆
1)堆的分类:
分为大堆、小堆
将根节点最大的堆叫做最大堆或者大根堆,将根节点最小的堆叫做最小堆或小根堆
优先级队列------堆(Heap)_第1张图片

2)堆的性质:
.堆中某个节点的值总是不大于或者不小于其父节点的值
.堆总是一颗完全二叉树
3)用途:
能够高效的获取到最大值/最小值,也能高效获取到第二大/第二小…
4)堆的存储
堆实质上是一个完全二叉树,一般是使用数组(下标从0开始)来存储这个二叉树的
对于这种存储方式,寻找双亲结点和孩子结点的方式(直接计算下标):

…如果i=0,则i表示的结点为根节点,否则i的双亲结点为(i-1)/2
…如果2i+1小于节点个数,则节点i的左孩子下标为2i+1,否则没有左孩子
…如果2i+2小于节点个数,则节点i的右孩子下标为2i+2,否则没有右孩子
…最后一个节点的父节点下标为:((length-1)-1)/2

2、堆的创建

  • 给定一个数组,整理成堆这样的结构,即转成完全二叉树并且使用堆的形式来存储,满足下标关系,并且满足堆(大堆/小堆特点)的关系

  • 可以使用向上调整/向下调整实现

  • 1)向下调整
    优先级队列------堆(Heap)_第2张图片
    如上图所示:0的左右子树都是大堆,此时可以进行向下调整
    调整过程:
    .从待调整的位置出发,取出该接待您的左右子树(通过下标换算方式)
    .该例是大堆,所以找出其左右孩子中的较大值,拿当前节点与该较大值进行比较,看这两个元素的大小关系是否符合“大堆”的要求,倘若不符合,交换这两个元素
    .将刚才被交换的位置作为新的起始位置, 再来找两个子节点的位置,并且找到较大值, 重复上述过程

注意:向下调整建堆时,必须是从后往前循环,从前往后是不行的
因为向下调整的前提条件是左右子树必须已经满足堆的要求了,才能向下调整根节点的位置

public class Heap {
    //向下调整(是创建堆的有一个核心操作)
    //前提条件:要求当前被调整节点的左右子树都已经是堆了
    //参数中传入数组,数组有效元素长度、待调整的起始位置
    //区别arr.length--->数组总的长度
    //按照大堆方式实现
    // 时间复杂度:O(logN)---(依次*2或者/2,其时间复杂度都与logN有关)
   private static void shiftDown(int[] arr,int size,int index){
        //待调整位置
        int parent=index;
        //待调整位置节点的左子树
        int child=2*index+1;
        while(child<size) {
            //让child指向左右子树值较大者
            if (child + 1 < size && child + 1 > child) {
                child = child+ 1;
            }
            //比较待调整元素和该左子树的值,如果左子树节点的值大于待调整位置的值,交换
            if (arr[child] > arr[parent]) {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
            } else {
                break;
            }
            //调整之后,更新待调整位置及该位置左子树的位置,以备下次循环
            parent = child;
            child = 2 * parent + 1;
        }
    }
    //使用向下调整建堆操作
    //时间复杂度O(N)(数学推导出来的)
    public void creatHeap(int[] array)  {
        //基于向下调整建堆
        //(1)从后往前遍历数组,针对每个下标都去向下调整
        for(int i=array.length-1;i>=0;i--){
            shiftDown(array,array.length,i);
        }
        //(2)代码优化:实际向下调整时,可从第一个非叶子节点进行向下调整(叶子节点没有向下调整的必要)
        //最后一个非叶子节点即最后一个节点的父节点,下标为(length-1)-1)/2
      for(int i=((array.length-1)-1)/2;i>=0;i--){
            shiftDown(array,array.length,i);
        }
    }
}
  • 2)向上调整与元素插入堆操作相关
    调整过程:
    调整的前提是该堆已经符合大堆/小堆要求,将待调整元素与该元素的父节点进行比较,根据大小堆规则进行交换,若还未达到要求,重复上述过程
    //二、 向上调整(以大堆为例)
    public void shiftUp(int[] arr,int size,int index){
        //创建一个child引用指向待调整位置
        int child=index;
        //创建一个parent引用指向其父节点的位置
        int parent=(child-1)/2;
        //当child=0时表示已经访问到根节点
        while(child>0){
            //比较待调整位置节点与父节点的大小
            if(arr[child]>arr[parent]){
                int tmp=arr[child];
                arr[child]=arr[parent];
                arr[parent]=tmp;
            }else{
                break;//满足要求,直接跳出循环
            }
            //更新child与parent,以便下次循环
            child=parent;
            parent=(child-1)/2;
        }
    }
    //使用向上调整(基于offer)的建堆操作
    public  void createHeap1(int[] arr){
        //最开始时,数组为空
        //循环遍历数组,把元素通过offer方法插入数组即可
        for(int x:arr){
            offer(x);
        }

    }

3、堆的核心操作—基于"向上调整"和"向下调整"来展开

1)元素插入堆

//往堆中插入元素
    //当前存储堆的数组
    private int[] arr=new int[100] ;
    private int size=0;
    //该方法需要用到成员变量,所以不加static,通过该方法向存储堆的数组末尾插入元素
    public void offer(int val){
        //数组满时,插入失败(此处不进行扩容操作)
        if(size>arr.length){
            return;
        }
        //尾插
        arr[size]=val;
        size++;

        //尾插完成之后进行向上调整
        shiftUp(arr,size,size-1);
    }

2)取堆顶元素

 //获取堆顶元素
    public Integer peek(){
        if(size==0){
            //数组为空
            return null;
        }
        return arr[0];
    }

3) 删除堆顶元素
注意:堆元素的删除操作只能是删除堆顶元素
思路:(移形换影)
将0号的元素,即堆顶元素与最后一个元素交换,然后size–,再将0号元素进行向下调整即可

 //删除堆顶元素
    public Integer poll(){
        if(size==0){
            return null;
        }
        //将0号元素与最后一个元素进行交换
        int result=arr[0];
        int tmp=arr[0];
        arr[0]=arr[size-1];
        arr[size-1]=tmp;
        size--;
        //从0号元素开始,往下进行向下调整
        shiftDown(arr,size,0);
        return result;
    }

你可能感兴趣的:(笔记,java,数据结构,优先级队列)