堆和优先级队列

1.堆

底层是一个数组,是一个完全二叉树按顺序存储的结果,对于非完全二叉树,则不必要用顺序存储的方法来存储,否则会有很多空间浪费。在来了解堆之前需要先对完全二叉树有一定的了解,才能来进一步深入地了解堆。

(1)分类:

有大根堆和小根堆之分。大根堆:根节点的val比左右孩子节点的val都要大;小根堆:根节点的val比左右孩子节点的val都要小。每一棵子树都是如此。

(2)如何创建一个堆:

eg:这里以创建大根堆为例子。

这样以后我们就创建好了一个大根堆。

代码实现:

public class MyHeap {
    public int[] arr;
    public int usedSize;
    public MyHeap() {
        this.arr = new int[10];
    }

    public void initArr(int[] array) {
        for(int i = 0; i < array.length; i++) {
            arr[i] = array[i];
            usedSize++;//别忘了
        }
    }

    public void createMaxHeap() {
        for(int parent = (arr.length - 1 - 1) / 2 ; parent >= 0; parent--) {
            //从最后一棵子树开始,使用向下调整
            softDown(parent, this.usedSize);
        }
    }
    //向下调整:时间复杂度:O(logn)
    private void softDown(int parent, int usedSize) {
        //计算左子树
        int child = parent * 2 + 1;
        while(child < usedSize) {
            if(child + 1 < usedSize && arr[child] < arr[child + 1]) {
                child++;
            }
            //如果是小根堆把这里的大于改成小于就好了
            if(arr[child] > arr[parent]) {
                //交换
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
                parent = child;
                child = parent * 2 + 1;
            }else {
                break;
            }
        }
    }
}

(3)堆的增,删,查操作:

a.增:(Offer)

添加元素的具体操作是在数组末尾添加进去,并且进行向上调整(与向下调整有异曲同工之处)

分析:

代码实现:

    private void softUp(int child) {
        int parent = (child - 1) / 2;
        while(child != parent) {
            if(arr[child] > arr[parent]) {
                int tmp = arr[child];
                arr[child] = arr[parent];
                arr[parent] = tmp;
                child = parent;
                parent = (child - 1) / 2;
            }else {
                break;
            }
        }
    }
    private boolean isFull() {
        return  usedSize == arr.length;
    }
    public void offer(int val) {
        if(isFull()) {
            arr = Arrays.copyOf(arr,usedSize * 2);
        }
        arr[usedSize] = val;
        softUp(usedSize);
        usedSize++;
    }

b.删

删除元素删的是堆的堆顶元素,具体操作是首先交换第一个元素与堆(数组)的最后一个元素,然后再进行向下调整(调整的是0下标的那棵树),注意此时堆的长度要减1。

代码实现:

private boolean isEmpty() {
    return usedSize == 0;
}
public int poll() {
    if(isEmpty()) {
        throw new RuntimeException("堆为空!");
    }
    int ret = arr[0];
    swap(arr,0,usedSize);
    usedSize--;
    softDown(0,usedSize);
    return ret;
}

c.查

查看元素就是获取堆的堆顶元素,具体操作是直接返回数组的首元数。

2.优先级队列

优先级队列就是基于堆来实现的。Java原生库中默认是小根堆(添加,删除元素都是按照小根堆的方式来调整整棵树的),这个小根堆是可以改变的。

(1)构造方法:

(2)常用方法:

注意: 

a.优先级队列这个类中没有实现empty方法,所以来判断是否为空只有一种方法,就是isEmpty。

b.往队列中插入的元素必须可以比较,否则会抛出ClassCastException。

c.不能往队列中插入null,否则会抛出NullPointerException。

d.删除和插入的时间复杂度都是O( \log_{2}N)

f.此处的优先级队列是线程不安全的,PriorityBlockingQueue是线程安全的。

g.使用前需要导入包java.util.PriorityQueue

(3)TopK问题:

返回前K个最小的数。这里提供两种方法:

法一:把所有的数都插入到队列,按照小根堆的方式,然后再出K次队列。时间复杂度为:

(N+K)O(\log_{2}N)

法二:把K个个数的数据建成大根堆,然后从第K个元素开始往队列插入,前提条件是要比堆顶元数要小,这样就能保证这个这个大根堆里就是前K个最小的数。实际做的时候也是选用这种方法,不选用法一。时间复杂度为:(N+K)O(\log_{2}K)

代码实现:

import java.util.PriorityQueue;
//建大根堆需要传入比较器
class maxHeap 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[] ret = new int[k];
        if(arr.length == 0 || k == 0) {
            return ret;
        }
        PriorityQueue<Integer> queue = new PriorityQueue<>(k,new maxHeap());
        //对前k个元素建立大根堆
        for(int i = 0; i < k; i++) {
            queue.offer(arr[i]);
        }
        for(int j = k; j < arr.length; j++) {
            if(queue.peek() > arr[j]) {
                //出队列
                queue.poll();
                queue.offer(arr[j]);
            }
        }
        for(int i = ret.length - 1; i >= 0; i--) {
            ret[i] = queue.poll();
        }
        return ret;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值