堆和优先级队列

目录

🐳今日良言:未经审视的人生不值得一过

🐇一、堆

🐍1.介绍

🐭2.步骤分析

🐝3.完整代码

🐯二、优先级队列

🐕1.介绍

🐂2.源码等相关分析


🐳今日良言:未经审视的人生不值得一过

🐇一、堆

🐍1.介绍

堆是一种数据结构,是一棵完全二叉树。

小根堆

根节点值总小于孩子节点的值,左右孩子节点的值无任何关系

大根堆 

根节点的值总大于孩子节点,左右孩子节点的值无任何关系

 

🐭2.步骤分析

我们以:集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据为例

该如何将上述二叉树调整成堆呢?

观察上面二叉树可以发现,根节点27的左右子树都满足小根堆的性质,因此,只需要将27向下调整到合适位置,这棵二叉树就可以调整成小根堆。

 根据上述分析,我们就可以写出向下调整的代码:

len是数组长度。

    public void shiftDown(int parent,int len) {
        int child = 2 * parent +1;// 左孩子节点
        // 不越界才调整
        while (child < len) {
            // 拿到左右孩子节点中数据较大的那个下标
            if (child + 1 < len && elem[child] > elem[child + 1]) {
                child++;
            }
            // 如果孩子节点的值大于根节点的值就交换
            if (elem[child] < elem[parent]) {
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;
                child = 2 * parent + 1;// 继续向下调整
            } else {
                // 如果不大于说明该树已经调整好了
                break;
            }
        }
    }

 上述代码时调整小根堆的代码,调整大根堆只需要修改两个地方即可。

elem[child] > elem[child + 1]   // >  修改成 < 即可

 if (elem[child] < elem[parent]) // 修改成 > 即可

如果左右子树都不是堆,该如何将这课二叉树调整成大根堆呢?

普通的序列{ 1,5,3,8,7,6 }为例

 数组长度为6,最后一位6下标为5,调整规则是,从最后一个叶子节点的父节点开始调整,所以从3开始调整:

 代码:

    // 创建大顶堆
    public void createHeap() {
        // 代表每次从哪个根节点开始
        for (int parent = (this.usedSize-1-1)/2;parent >= 0;parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

接下来,分析一下,上述建堆的时间复杂度:

 

 如何在已构建好的堆中插入数据呢?例:再已构建好的小根堆中插入元素10

插入规则:如果是数组的话先要判断数组是否已经满了,满了的话就需要扩容,没满的话就先将待插入数据放到最后,然后进行下面操作:

向上调整代码:

    // 向上调整
    public void siftUp(int child) {
        int parent = (child  - 1) /2;
        while (child > 0) {
            if (this.elem[child] > this.elem[parent]) {
                int temp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = temp;
                child = parent;
                parent = (child - 1) /2;
            } else {
                break;
            }
        }
    }

 插入操作代码:

    // 添加数据
    public void offer(int val) {
        // 首先需要判满
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        // 然后放在最后usedSize位置
        this.elem[usedSize] = val;
        this.usedSize++;
        // 然后开始向上调整
        siftUp(usedSize - 1);
    }

 删除操作:堆的删除一定删除的是堆顶元素。

假如要删除10

 第一步:将10与最后的数据28交换位置,

第二步:将堆中有效数据个数减少一个

第三步:从根节点处开始向下调整即可。

删除操作代码:

  // 删除堆顶数据
    public int pop() {
        // 判空
        if (isEmpty()) {
           return -1;
        }
        // 然后交换堆顶和最后一个数据
        int temp = this.elem[0];
        this.elem[0] = this.elem[usedSize - 1];
        this.elem[usedSize - 1] = temp;
        usedSize--;
        // 从堆顶开始向下调整
        shiftDown(0,usedSize);
        return temp;
    }

🐝3.完整代码

import java.util.Arrays;


class Heap {
    public int[] elem;// 数组
    public int usedSize;// 有效数据的个数
    public static final int DEFAULT_SIZE = 10;
    public Heap(int[] array) {
        this.elem = new int[DEFAULT_SIZE];
        for (int i = 0; i < array.length;i++) {
            this.elem[i] = array[i];
            usedSize++;
        }
    }
    // 创建大顶堆
    public void createHeap() {
        // 代表每次从哪个根节点开始
        for (int parent = (this.usedSize-1-1)/2;parent >= 0;parent--) {
            shiftDown(parent,this.usedSize);
        }
    }

    /**
     * 真正的向下调整方法
     * @param parent 每次调整的根节点
     * @param len    每棵子树调整的位置   < len
     */
    public void shiftDown(int parent,int len) {
        int child = 2 * parent +1;// 左孩子节点
        // 不越界才调整
        while (child < len) {
            // 拿到左右孩子节点中数据较大的那个下标
            if (child + 1 < len && elem[child] > elem[child + 1]) {
                child++;
            }
            // 如果孩子节点的值大于根节点的值就交换
            if (elem[child] < elem[parent]) {
                int temp = elem[child];
                elem[child] = elem[parent];
                elem[parent] = temp;
                parent = child;
                child = 2 * parent + 1;// 继续向下调整
            } else {
                // 如果不大于说明该树已经调整好了
                break;
            }
        }
    }
    // 添加数据
    public void offer(int val) {
        // 首先需要判满
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }
        // 然后放在最后usedSize位置
        this.elem[usedSize] = val;
        this.usedSize++;
        // 然后开始向上调整
        siftUp(usedSize - 1);
    }
    // 向上调整
    public void siftUp(int child) {
        int parent = (child  - 1) /2;
        while (child > 0) {
            if (this.elem[child] > this.elem[parent]) {
                int temp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = temp;
                child = parent;
                parent = (child - 1) /2;
            } else {
                break;
            }
        }
    }
    // 判满
    public boolean isFull() {
        return this.usedSize == this.elem.length;
    }
    // 删除堆顶数据
    public int pop() {
        // 判空
        if (isEmpty()) {
           return -1;
        }
        // 然后交换堆顶和最后一个数据
        int temp = this.elem[0];
        this.elem[0] = this.elem[usedSize - 1];
        this.elem[usedSize - 1] = temp;
        usedSize--;
        // 从堆头开始向下调整
        shiftDown(0,usedSize);
        return temp;
    }
    public boolean isEmpty() {
        return this.usedSize == 0;
    }
}
public class TextHeap {
    public static void main(String[] args) {
        int[] array = {27,15,19,18,28,34,65,49,25,37};
        Heap heap = new Heap(array);
        heap.createHeap();
        //heap.offer(80);
        System.out.println(heap.pop());
        System.out.println(Arrays.toString(heap.elem));
    }
}

🐯二、优先级队列

🐕1.介绍

Java 集合框架中提供了 PriorityQueue PriorityBlockingQueue 两种类型的优先级队列, PriorityQueue 是线 程不安全的, PriorityBlockingQueue 是线程安全的 ,这里介绍 PriorityQueue

🐂2.源码等相关分析

使用PriorityQueue的几点注意事项:

1.使用之前必须导入包:import java.util.PriorityQueue;

2.放置的元素必须是能够比较大小的,否则会报异常,如图:

 3.不能插入空对象,会报异常。

4.默认是建小堆。

创建一个空的优先级队列:

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();

观察一下它的底层容量等。

 当不传入比较器时,会创建一个默认容量为11的数组。

再来看一下扩容规则:

然后插入一些数据:

PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(10);
priorityQueue.offer(5);

 通过上述了解,提出一个问题:如何可以创建大根堆呢?

如果是自定义类型其实只需要修改compareTo里面相减的返回值即可

因为Integer内部实现了compareTo方法,我们想要让其可以创建出大根堆,可以传入一个比较器。

这里先补充一下compareable和comparetor的区别:

面试官:元素排序Comparable和Comparator有什么区别? - 腾讯云开发者社区-腾讯云 (tencent.com)

上述链接博主写的非常详细。 

我们这里创建一个比较器 

class Intcmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1;
    }
}

 在创建PriorityQueue 时传入比较器

此时再来分析一下插入数据:

 

除了可以通过创建自定义比较器外,还可以通过匿名类的方式,更快速、便捷的完成自定义比较器的功能。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值