堆和优先级队列

存储方式

在一个集合的数据中,找出最大值或者最小值

逻辑上完全二叉树
实际上是可以通过数组保存的
满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆 ,反之就是小堆
在这里插入图片描述

使用数组保存二叉树结构,方式即将二叉树用层序遍历方式放入数组中,一般只适合表示完全二叉树,因为非完全二叉树会有空间的浪费,这种方式的主要用法就是堆的表示

下标关系

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

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

操作-向下调整(示例:建立一个小堆)

它的搜索比较是往下搜索比较

向下调整的前提条件是,除了需要调整的位置和它的两个孩子不知道是否满足大堆的条件,但其他地方一定满足
小堆不一定有序,有序(升序的)的一定是小堆
向下调整就是一个皇帝便乞丐的过程

在这里插入图片描述

  • 判断index(需要调整的位置)的位置是不是叶子结点,如果是,直接返回
  • 找到两个孩子中的最小值
    小孩子的值和index 的位置进行比较
  • 如果小孩子的值 ==/ > index的值直接return
  • < 就直接交换,把最小的孩子视为index 再次循环
/**
     * 向下调整
     * 因为向下调整是有前提的,所以需要建堆
     * 时间复杂度 最坏就是log(n)
     * @param array
     * @param size
     * @param index
     */
    public static void adjustDown(int[] array,int size,int index){
        int leftIndex = 2*index+1;
        //1.判断index 是不是叶子
        if(leftIndex >= size) return;//说明需要调整的结点是叶子结点 直接跳出

        //现在已经确定了它有左孩子
        //2.找最小的的孩子
        //需要找到它的孩子里面的最小值 --所以需要判断有没有右孩子
        int midIndex = leftIndex;//这种先赋值之后再次进行更改的做法比较不错,可以学习
        int rightIndex = leftIndex+1;
        if(rightIndex < size && array[rightIndex] < array[index]){
            //说明最小的孩子是右孩子
            midIndex = rightIndex;
        }

        //3.比较最小孩子的值
        if(array[index] <= array[midIndex]){
            return;
        }


        //4.交换
        int t = array[index];
        array[index] = array[midIndex];
        array[midIndex] = t;

        //5.把最小的孩子看作index 继续循环
        index = midIndex;

        adjustDown(array,size,index);
   }

写代码注意事项

  • 怎么判断index的位置是不是叶子(原来的想法是,看它的左右结点是不是null,但是思想误区是现在是一个数组,不能用是不是null来判断)而是和size判断
    所以没有孩子就是叶子结点,同时又因为是一个完全二叉树,所以没有左孩子就说明是一个叶子结点。
  • 确定有左孩子之后,就去判断有没有右孩子

操作-向上调整(示例:建立大堆)

搜索比较是向上搜索比较

向上调整只需要和它的父节点比较即可,如果比父节点大,就交换。是一个类似乞丐变皇帝的过程

/**
     * 向上调整
     * index 是需要向上调整的起始位置
     * 或者也可以写成循环的方式
     */
    public static void adjustUp(int[] array,int size,int index){
        //如果是要调整的是根调整结束
        if(index == 0) return;
        //index父节点
        int parentIndex = (index -1)/2; // 这里的下标永远大于等于0
        //和父亲比较
        if (array[parentIndex] <= array[index]){
            return;
        }
        //交换
        int t = array[index];
        array[index] = array[parentIndex];
        array[parentIndex] = t;
        //把父节点看作index 接续循环
        index = parentIndex;
        adjustUp(array,size,index);
    }

建堆(小堆)

基于向下调整(向下搜索–找孩子) 的方式建堆,遍历方式必须从后往前遍历
基于向上调整(向上搜索–找父亲)的方式建堆,遍历方式必须从前往后遍历

对一个数组利用向下调整的方式进行建堆,关键就是找到第一个非叶子结点,然后从第一个非叶子结点开始依次向下调整。采用向下调整的过程必须是从后往前遍历。(因为向下调整一定要满足一个前提条件,就是除了要调整的位置,其他满足了堆关系)。

建堆
* 找到第一个非叶子结点
* 第一个非叶子结点其实就是最后一个结点的的父节点

 public static void createHeap(int[] array,int size){
        //找到最后一个非叶子结点,然后不断进行向下调整
        int lastIndex = size -1;
        int lastParentIndex = (lastIndex -1)/2;
        //从【lastParent ,0】 不断地进行向下调整
        for(int i = lastParentIndex;i >= 0;i--){
            adjustDown(array,size,i); //使用上面的小堆代码
        }
    }

优先队列(示例建小堆)

优先级队列的实现方式有很多,但最常见的是使用堆来构建。

  • 入队列的时候,就把元素插入到数组末尾,然后向上调整。
  • 进行出队列的时候,就把堆顶的元素删除同时让最后一个元素来顶替第一个元素的位置,并且进行向下调整

时间复杂度

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

入队列

/**
     * 插入的操作
     * 采用尾插,使用向上调整
     * 乞丐变皇帝 o(log n)
     */
    public void add(Integer e){
        array[size] = e;
        size++;
        adjustUp(size-1);
    }
    private void adjustUp(int index){
        if(index == 0) return;//要调整的是不是根
        while (index != 0){
            int parentIndex = (index -1)/2; // 这里的下标永远大于等于0
            //和父亲比较
            if (array[parentIndex] <= array[index]){
                return;
            }
            //交换
            int t = array[index];
            array[index] = array[parentIndex];
            array[parentIndex] = t;
            //把父节点看作index 接续循环
            index = parentIndex;
        }
    }

出队列

/**
     *删除实现的操作就是 把数组的第一个元素删除,
     * 然后让最后一个元素来顶替第一个元素的位置,之后进行向下调整即可
     */
    public Integer remove(){
        if(size == 0){
            throw  new RuntimeException("数组是空的");
        }
        int e = array[0];
        array[0] = array[size-1];

        //维护size
        size--;
        //向下调整
        //只是说这里的向下调整的index变成了0的位置
        //Priority.adjustDown();或者去调用之前写好的
        adjustDown(0);
        return e;
    }
    private void adjustDown(int index){
        int leftIndex = index * 2+1;
        if(leftIndex >= size) return;
        int minIndex = leftIndex;
        int rightIndex = leftIndex+1;
        if(rightIndex < size && array[rightIndex] < array[leftIndex]){
            minIndex = rightIndex;
        }
        if(array[index] <= array[minIndex]) {
            return;
        }
        //交换
        int t = array[index];
        array[index] = array[minIndex];
        array[minIndex] = t;
        index = minIndex;
        adjustDown(index);
    }

堆的应用

1.TopK问题

海量数据,找到集合中最大的前几个
* 用大堆还是小堆-- 用小堆
* 海量数据的问题决定了我们不能对所有数据进行建堆
* 建立一个五个数据的小堆,每一个要比较的数据都和这个堆里面堆顶去比较
* 如果比堆顶大,那么就可以替换这个堆顶,然后对堆进行向下调整即可。

TopK 参考回答

2. 堆排序

待补充

3. 自定义类堆的构建

  /**
     * 堆的应用3
     * 由于堆的构建,需要比较以及调整的能力
     * 所以如果用自定义类进行堆的构建,那么需要实现比较能力
     * 自定义类
     */
    public static void main(String[] args) {
        // PriorityQueue<Person> queue = new PriorityQueue<Person>();

        //本质上不是在new 接口,而是new了一个匿名内部类
        //按照年龄
        /*PriorityQueue<Person> queue = new PriorityQueue<Person>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.age - o2.age;
            }
        });*/

        //按照名字的字典序
        /*PriorityQueue<Person> queue = new PriorityQueue<Person>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.name.compareTo(o2.name);
            }
        });*/

        //简化lambda表达式
        PriorityQueue<Person> queue = new PriorityQueue<Person>(
                (Person o1, Person o2) -> {
                    return o1.age - o2.age;
                }
        );


        Person p1 = new Person("gb",35);
        Person p2 = new Person("cpx",34);
        Person p3 = new Person("tz",45);
        queue.add(p1);
        queue.add(p2);
        queue.add(p3);
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
    }
class Person implements Comparable<Person>{ //注意这个类为什么是这样写的,相当于是用了泛型
    String name;
    int age;
    Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    @Override
    public int compareTo(Person o) {
        //由于上面使用了泛型,所以这里就不用传递泛型了
        return age -o.age;
    }
}

JDK的优先队列

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

public static void main1(String[] args) {
        MyPriorityQueue queue = new MyPriorityQueue();
        //PriorityQueue<Integer> queue= new PriorityQueue<>();
        queue.add(3);
        queue.add(5);
        queue.add(2);
        queue.add(7);
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值