【Java】—— 堆

一、堆的定义

在计算机科学中,堆(heap)是一种特殊的树状数据结构。用于存储和管理数据。堆通常用于实现优先队列。其中具有最高(或最低)优先级的元素始终位于堆的根部。

堆分为最小堆和最大堆两种类型:

  • 最小堆:父节点的值小于或等于其子节点的值。
  • 最大堆:父节点的值大于或等于其子节点的值。

二、堆的特性

1、堆是一个完全二叉树,通过数组的方式来存储的完全二叉树。将这个树层序遍历,遍历的结果依次添加到数组中,遇到 空节点 则将 空节点 也添加到数组中

放进 null 是为了维护父子之间的下标关系,可以通过下标换算出父子关系

设 父节点下标为 parent ,左子树的下标就是 2*parent+1 ,右子树的下标就是 2*parent+2

设 子节点下标为 child 父节点的下标(child - 1) / 2

(普通的二叉树可能包含大量的 null 导致内存利用率低所以不使用数组表示)

2、对于堆来说,任何一个节点,值都小于(或大于)两个子节点的值,两个子节点的值谁大谁小没关系。树的树根节点( 数组下标为[0] )的元素,就是整个树的 最小值/最大值。

三、堆的实现

1、向下调整/下沉式调整

有一个数组,除了 [0] 元素之外,其他的元素都满足小堆的要求

针对上述情况,就需要用到 向下调整

第一步:先确定要比较的节点是哪一个,如果只有左子树,就直接和左子树比,如果同时有左右子树,先比较左右子树中更小的那一个

第二步:拿着更小的节点和根节点进行比较,如果根节点比子节点大,交换两个节点的值。

2.构建堆的基本思路

从最后一个非叶子节点出发,从后往前遍历,针对每个节点进行向下调整

package PriorityQueue;

import java.util.Arrays;

public class Heap {
    /**
     *向下调整
     * @param array 要调整的堆
     * @param size 堆的大小
     * @param index 从哪个位置开始向下调整
     */
    public static void shiftDown(int[] array,int size,int index){
        //要调整的父节点
        int parent = index;
        //计算要调整的左子树节点
        int child = 2 * parent + 1;
        //如果子节点下标超出数组范围,说明子节点不存在
        //主循环就结束,调整就完成了
        while (child < size){
            //左子树+1得到右子树,通过这个判断找出左右子树更小的值
            if(child + 1 < size && array[child + 1] < array[child]){
                child = child + 1;
            }
            //然后比较 child 和 parent 的大小关系
            if(array[parent] <= array[child]){
                break;
                //父节点比子节点小,已经符合小堆规则,不需要再调整了
            } else {
              //交换父子的值
              int tmp = array[parent];
              array[parent] = array[child];
              array[child] = tmp;
              //更新 parent 和 child 的指向
              parent = child;
              child = 2 * parent + 1;
            }
        }
    }

    /**
     * 建堆操作
     * @param array
     */
    public static void creatHeap(int[] array){
        //1.找到最后一个非叶子节点
        //2.从后往前循环进行向下调整
        int lastLeaf = array.length - 1;
        int lastParent = (lastLeaf - 1) / 2;
        for(int i = lastParent;i>=0;i--){
            shiftDown(array,array.length,i);
        }
    }

    /**
     * 向上调整堆,整个数组只有最后一个元素不满足堆的要求
     * 在这个前提下,堆最后一个元素进行向上调整
     * @param array
     * @param index
     */
    public static void shiftUp(int[] array,int index){
        int child = index;
        int parent = (child - 1) / 2;
        //child为0,说明已经到根节点了,循环就结束了
        while (child > 0){
            if(array[parent] > array[child]){
                //此时应该要进行交换
                int tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
                //更新 child 和 parent 的指向
                child = parent;
                parent = (child - 1) / 2;
            }else {
                //此时符合堆,不需要再调整了
                break;
            }
        }
    }

    public static void main(String[] args) {
        int[] array = {1,3,2,6,5,7,8,9,10,0};
        creatHeap(array);
        System.out.println(Arrays.toString(array));
    }
}

可以看到最后的结果已经满足堆的要求了,整体的建堆时间复杂度是 O(N)

四、优先级队列

队列的核心操作:

1.入队列

        新添加的元素可能会打破原有的堆的规则,针对新来的元素就需要进行 “向上调整”

    /**
     * 向上调整堆,整个数组只有最后一个元素不满足堆的要求
     * 在这个前提下,堆最后一个元素进行向上调整
     * @param array
     * @param index
     */
    private static void shiftUp(int[] array,int index){
        int child = index;
        int parent = (child - 1) / 2;
        //child为0,说明已经到根节点了,循环就结束了
        while (child > 0){
            if(array[parent] > array[child]){
                //此时应该要进行交换
                int tmp = array[parent];
                array[parent] = array[child];
                array[child] = tmp;
                //更新 child 和 parent 的指向
                child = parent;
                parent = (child - 1) / 2;
            }else {
                //此时符合堆,不需要再调整了
                break;
            }
        }
    }


public class MyPriorityQueue {
    private int[] arrar;
    public int size = 0;
    public MyPriorityQueue(){
        arrar = new int[1000];
    }
    //入队列
    public void offer(int value){
        if(size == arrar.length){
            //这里还可以实现一个扩容功能
        }
        //先把新元素进行尾插
        arrar[size] = value;
        size++;
        //从最后的元素开始向上进行调整
        Heap.shiftUp(arrar,size - 1);
    }
}

2.出队列

队首就是 [0] 元素,需要把这个元素删除掉,但是在堆中,直接删掉根节点,后续的节点往期走就有可能导致堆的规则发生改变。而最后一个节点删除不会造成其他影响,所以可以通过先交换首尾的值,再对尾元素删除,最后对首进行向下调整。

//出队列
    public Integer poll(){
        if(size == 0){
            return null;
        }
        //先把第一个元素和最后一个元素交换
        int ret = arrar[0];
        arrar[0] = arrar[size - 1];
        //这一步写不写无所谓,因为size--就把这个元素删了
        arrar[size - 1] = ret;
        size--;
        Heap.shiftDown(arrar,size,0);
        return ret;
    }

3.取队首元素

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

测试

public class text2 {
    public static void main(String[] args) {
        MyPriorityQueue queue = new MyPriorityQueue();
        queue.offer(3);
        queue.offer(4);
        queue.offer(2);
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

4.给优先级队列指定比较规则

Java标准库中有这样两个接口:ComparableComparator 就是用来描述比较规则的

Comparable : 如果队列插入的是类的对象,就可以让这个类实现 Comparable 接口

(内部制定规则,比较自己和别人)

Comparator : 如果队列插入的是 int 或 String 这种,此时就可以实现 Comparator 来完成上述规则

(第三方,比较两个内容)

写法一:

                                //泛型参数取决于你要比较什么
class IntComparator implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        //此处是自定义比较规则,原来的规则就不要了
        //如果判断 o1 比 o2 小,就返回一个 < 0 的整数
        //如果判断 o1 比 o2 大,就返回一个 > 0 的整数.
        //如果判断 o1 比 o2 相等,就返回 0
        //这里就是定义了大堆的比较规则,o1 - o2就是小堆
        return o2 - o1;
    }
}

写法二:

//这个写法是“匿名内部类”写法,实现了 Comparator 接口,重写了 Comparator 的 compare 方法,同时创建了内部类的实例
        //只能创建一次!!
        PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });

使用优先级队列保存自定义的类型

package PriorityQueue;

import java.util.PriorityQueue;


class Student implements Comparable<Student>{

    public int id;
    public String name;
    public int score;

    public Student(int id,String name,int score){
        this.id = id;
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", =name='" + name + '\'' +
                ", score=" + score +
                '}' + '\n';
    }

    //对于 Comparator 来说,是有两个参数,比较 o1 和 o2 的关系
    //对应 Comparable 来说,看起来是一个参数,实际上是拿 this 和 o 进行比较
    //使用 Comparable 的前提就是这个类是你自己写的
    @Override
    public int compareTo(Student o) {
        //大堆的写法,比较分数
        return o.score - this.score;
    }
}


public class text3 {
    public static void main(String[] args) {
        PriorityQueue<Student> queue = new PriorityQueue<>();
        queue.offer(new Student(1,"Alice",88));
        queue.offer(new Student(2,"Bob",90));
        queue.offer(new Student(3,"Charlie",70));
        queue.offer(new Student(4,"David",85));
        System.out.println(queue.toString());
    }
}

还可以同时写多种 Comparator 种比较方法

    class StudentComparator1 implements Comparator<Student>{
        @Override
        public int compare(Student o1,Student o2){
            return o2.score - o1.score;
        }
    }
    class StudentComparator2 implements Comparator<Student>{
        @Override
        public int compare(Student o1,Student o2){
            return o2.id - o1.id;
        }
    }
    class StudentComparator3 implements Comparator<Student>{
        @Override
        public int compare(Student o1,Student o2){
            return o1.name.compareTo(o2.name);
        }
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值