优先级队列、堆

堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储,注意:对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。

这个就是非完全二叉树,不适合用堆

将元素存储到数组中后,可以根据二叉树章节的性质5对树进行还原。假设i为节点在数组中的下标,则有:
如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子 

但是在堆中又分为大根堆和小根堆两种:

大根堆

 这个就是大根堆,对于任意节点 i,其值(通常称为键值或权重)大于或等于其所有子节点的值。换句话说,每个节点的值都至少大于其两个子节点的值。根节点(即树的顶端)具有整个堆中的最大值。

这个就是小根堆,在小根堆中,对于任意节点 i,其值(或称键值、权重)小于或等于其所有子节点的值。换言之,每个节点的值都不大于其两个子节点的值。堆的根节点(顶部节点)拥有整个堆中最小的值。

那么我们了解了什么是大根堆和小根堆,那么怎么用代码实现出来呢?下面我举例大根堆的创建,请看下面代码

大根堆的创建

public class TestHeap {
    public int elem[];
    public int usedSize;
 
    public TestHeap(){
        this.elem = new int[10];
    }
    public void init(int[] array){
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }
 
    public void creatHeap(){
        for (int parent = (usedSize-1-1)/2; parent >=0 ; parent--) {
            shifDown(parent,usedSize);
        }
    }
 
    private void swap(int i,int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
    public void shifDown(int parent,int end){
        int child = parent*2+1;
        while(child < end){
            if(child+1<end && elem[child] < elem[child+1]){
                child++;
            }
            //child就是左右孩子的最大值
            if(elem[child] > elem[parent]){
                swap(child,parent);
                parent = child;
                child = parent*2+1;
            } else {
                break;
            }
        }
    }
 
}

让我来解释一下这段代码:

首先creatHeap函数用来把当前最后一个元素的根节点和数组的长度传过去

swap函数用来交换两个元素的位置

ShifDown函数向下调整,可以根据传过来的根节点确定孩子结点,第一个if语句用来判断左右孩子哪个大,执行完这个if语句,child指向的就是大的那个结点,第二个if语句用来比较根节点和孩子结点哪个大,如果孩子结点比根结点大,那么利用swap函数进行交换,然后根结点的位置变成当前孩结点的位置,根据前面的公式可得,孩子结点的位置变成当前根节点的*2+1,如果走到最后孩子结点的下标比数组长度大,那么退出循环,进行下一次的creatHeap

堆的插入

堆的插入总共需要两个步骤:
1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
2. 将最后新插入的节点向上调整,直到满足堆的性质

//堆的插入
    public void offer(int val){
        if(isFull()){
            elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize] = val;
        usedSize++;
        shifUp(usedSize-1);
    }

    public void shifUp(int child){
        int parent = (child-1)/2;
        while(parent>=0){
            if(elem[parent] < elem[child]){
                swap(parent,child);
                child = parent;
                parent = (child-1)/2;
            } else {
                break;
            }
        }
    }

    public boolean isFull(){
        return elem.length == usedSize;
    }
    private void swap(int i,int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

 堆的删除

注意:堆的删除一定删除的是堆顶元素。具体如下:
1. 将堆顶元素对堆中最后一个元素交换
2. 将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整

public int poll(){
    if(isEmpty()){
        return -1;
    }
    int old = elem[0];
    swap(0,usedSize-1);
    usedSize--;
    shifDown(0,usedSize);
    return old;
}

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

private void swap(int i,int j){
    int tmp = elem[i];
    elem[i] = elem[j];
    elem[j] = tmp;
}

public void shifDown(int parent,int end){
    int child = parent*2+1;
    while(child < end){
        if(child+1<end && elem[child] < elem[child+1]){
            child++;
        }
        //child就是左右孩子的最大值
        if(elem[child] > elem[parent]){
            swap(child,parent);
            parent = child;
            child = parent*2+1;
        } else {
            break;
        }
    }
}

PriorityQueue:

关于PriorityQueue的使用要注意:
1. 使用时必须导入PriorityQueue所在的包,即:
2. PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出
ClassCastException异常
3. 不能插入null对象,否则会抛出NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. PriorityQueue底层使用了堆数据结构
6. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
        return o2-o1;
    }
}
函数名 功能介绍
boolean
offer(E e)
插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度O(log2^{N}) ,注意:空间不够时候会进行扩容
E peek()获取优先级最高的元素,如果优先级队列为空,返回null
E poll()移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size() 获取有效元素的个数
void
clear() 
清空
boolean
isEmpty() 
检测优先级队列是否为空,空返回true

堆排序 以及 最小k个数链接

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆
降序:建小堆

我们可以来一道例题来学习堆排序

. - 力扣(LeetCode)

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都
不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

我们已经了解了实现的大概原理,那我们来看一下代码是如何实现的吧!

class IntCmp implements Comparator<Integer>{
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}
class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] tmp = new int[k];
        if(k == 0){
            return tmp;
        }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());
        for (int i = 0; i < k; i++) {
            maxHeap.offer(arr[i]);
        }
        for (int i = k; i < arr.length; i++) {
            int val = maxHeap.peek();
            if(val>arr[i]){
                maxHeap.poll();
                maxHeap.offer(arr[i]);
            }
        }


        for (int i = 0; i < k; i++) {
            tmp[i] = maxHeap.poll();
        }
        return tmp;
    }

}

代码解释:

1.首先实现大根堆必须传入一个自定义的 Comparator,该比较器定义了元素之间的比较逻辑,使得较大的元素被认为具有更高的优先级。

2.将k个元素放入队列中

3.将队列的顶元素分别与没进入队列的元素依次比较,向下调整

4.进行到这一步,说明队列中已经是最小的k个元素了,把他们依次放入tmp列表,最后返回就行了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值