堆(Heap)与优先队列

本文介绍了堆的基本概念,包括大顶堆和小顶堆的性质,并详细阐述了堆的插入和删除操作。堆常用于实现优先队列,如Java中的PriorityQueue类。文章通过LeetCode题目,展示了如何利用堆解决数据流中的第K大元素、数组中的第K个最大元素等实际问题,强调了堆在维护集合最值方面的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

堆(Heap)与优先队列

堆的基础知识

堆是一个完全二叉树,可以采用数组形式存储

下标从0开始,i位置的节点:

左孩子坐标为:2 * i + 1;

右孩子坐标为:2 * i + 2;

父亲节点坐标为:(i - 1) / 2;

Java中的PriorityQueue类(优先队列)是堆的实现类

大顶堆:

性质:

在二叉树任意的三元子中,根节点都大于两个子节点的堆

最大值在堆顶,第二大的值在根节点的左节点或右节点中。

兄弟之间无大小关系

堆适合维护:集合最值

小顶堆:

性质:

在二叉树任意的三元子中,根节点都小于两个子节点的堆

最小值在堆顶,第二小的值在根节点的左节点或右节点中。

兄弟之间无大小关系

堆—尾部插入调整(插入元素)

将新节点不断地与其父节点比较,新节点大于父节点则与父节点交换位置。

堆—头部弹出调整(删除元素)

大顶堆:弹出的是堆中的最大值

小顶堆:弹出的是堆中的最小值

在弹出堆顶元素之后,数组长度应该减一。原0下标的元素因为被弹出,所以为空,那么我们就把原数组中末尾的元素**(为了方便将其记为a)插入到下标0的位置上去。随后我们要进行堆性质的维护,每一次都从三元组中选择一个最大的节点与a交换,直到元素a**是三元组中最大的节点。

堆排序

—待完善

堆–优先队列的一种实现方式

普通队列(最大/最小)堆
尾部入队尾部可以插入
头部出队头部可以弹出
先进先出每次出队权值(最大/最小的元素)
数组实现数组实现,逻辑上看成一个堆

堆的代码实现

大顶堆:

import java.util.Arrays;
class BigHeap{
    int size;
    int[] bigHeap;
    public BigHeap() {
        this.size = 0;
        this.bigHeap = new int[10];//默认是10大小
    }
    public BigHeap(int n) {
        this.size = 0;
        this.bigHeap = new int[n];
    }
    //返回堆大小的方法
    public int size(){
        return size;
    }
    //返回堆顶元素的方法
    public int top(){
        return bigHeap[0];
    }
    //向堆中插入元素的方法
    public void push(int num){
        if(size == bigHeap.length) return;//数组满了无法插入
        bigHeap[size] = num;
        int index = size++;//堆中元素数量加一,index为最后一个元素的下标
        while(index > 0 && bigHeap[(index - 1) / 2] < num){//保证下标不越界并且可以比较大小
            swap(bigHeap,index,(index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
    //删除堆顶元素的方法
    public int pop(){
        if(size == 0)
            return 0;
        int num = bigHeap[0];
        bigHeap[0] = bigHeap[size - 1];
        bigHeap[size - 1] = 0;//这里不写也是可以的,写了主要是为了toString观看方便,因为插入新值的时候会把老值覆盖掉
        size -= 1;
        int n = size - 1;//n为数组中最后一个元素的下标
        int index = 0;//这是要向下进行置换的元素
        while((index * 2 + 1) <= n){//判断是否越界
            int temp = index;
            if(bigHeap[temp] < bigHeap[index * 2 + 1]) temp = index * 2 + 1;
            if((index * 2 + 2) <= n && bigHeap[temp] < bigHeap[index * 2 + 2]) temp = index * 2 + 2;
            swap(bigHeap,temp,index);
            if(index == temp) break;
            index = temp;
        }
        return num;
    }

    //交换两个元素的方法
    public void swap(int[] nums,int index1,int index2){
        int number = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = number;
    }

    @Override
    public String toString() {
        return "BigHeap{" +
                "bigHeap=" + Arrays.toString(bigHeap) +
                '}';
    }
}

测试:

public class Main{
    public static void main(String args[]){
        BigHeap bigHeap = new BigHeap();
        bigHeap.push(4);
        bigHeap.push(5);
        bigHeap.push(1);
        bigHeap.push(9);
        bigHeap.push(97);
        bigHeap.push(3);
        while (bigHeap.size() != 0){
            System.out.println("当前堆顶为: " + bigHeap.pop() + "     出栈后堆为" + bigHeap.toString());
        }
    }
}
当前堆顶为: 97     出栈后堆为BigHeap{bigHeap=[9, 5, 3, 4, 1, 0, 0, 0, 0, 0]}
当前堆顶为: 9     出栈后堆为BigHeap{bigHeap=[5, 4, 3, 1, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5     出栈后堆为BigHeap{bigHeap=[4, 1, 3, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 4     出栈后堆为BigHeap{bigHeap=[3, 1, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 3     出栈后堆为BigHeap{bigHeap=[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 1     出栈后堆为BigHeap{bigHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

小顶堆:

import java.util.Arrays;
class SmallHeap {
    int size;
    int[] smallHeap;
    public SmallHeap() {
        this.size = 0;
        this.smallHeap = new int[10];//默认是10大小
    }
    public SmallHeap(int n) {
        this.size = 0;
        this.smallHeap = new int[n];
    }
    //返回堆大小的方法
    public int size(){
        return size;
    }
    //返回堆顶元素的方法
    public int top(){
        return smallHeap[0];
    }
    //向堆中插入元素的方法
    public void push(int num){
        if(size == smallHeap.length) return;//数组满了无法插入
        smallHeap[size] = num;
        int index = size++;//堆中元素数量加一,index为最后一个元素的下标
        while(index > 0 && smallHeap[(index - 1) / 2] > num){//保证下标不越界并且可以比较大小
            swap(smallHeap,index,(index - 1) / 2);
            index = (index - 1) / 2;
        }
    }
    //删除堆顶元素的方法
    public int pop(){
        if(size == 0)
            return 0;
        int num = smallHeap[0];
        smallHeap[0] = smallHeap[size - 1];
        smallHeap[size - 1] = 0;//这里不写也是可以的,写了主要是为了toString观看方便,因为插入新值的时候会把老值覆盖掉
        size -= 1;
        int n = size - 1;//n为数组中最后一个元素的下标
        int index = 0;//这是要向下进行置换的元素
        while((index * 2 + 1) <= n){//判断是否越界
            int temp = index;
            if(smallHeap[temp] > smallHeap[index * 2 + 1]) temp = index * 2 + 1;
            if((index * 2 + 2) <= n && smallHeap[temp] > smallHeap[index * 2 + 2]) temp = index * 2 + 2;
            swap(smallHeap,temp,index);
            if(index == temp) break;
            index = temp;
        }
        return num;
    }

    //交换两个元素的方法
    public void swap(int[] nums,int index1,int index2){
        int number = nums[index1];
        nums[index1] = nums[index2];
        nums[index2] = number;
    }

    @Override
    public String toString() {
        return "SmallHeap{" +
                "smallHeap=" + Arrays.toString(smallHeap) +
                '}';
    }
}

测试:

public class Main{
    public static void main(String args[]){
        SmallHeap smallHeap = new SmallHeap();
        smallHeap.push(4);
        smallHeap.push(5);
        smallHeap.push(1);
        smallHeap.push(9);
        smallHeap.push(97);
        smallHeap.push(3);
        while (smallHeap.size() != 0){
            System.out.println("当前堆顶为: " + smallHeap.pop() + "     出栈后堆为" + smallHeap.toString());
        }
        smallHeap.push(1);
        smallHeap.push(5);
        smallHeap.push(-3);
        while (smallHeap.size() != 0){
            System.out.println("当前堆顶为: " + smallHeap.pop() + "     出栈后堆为" + smallHeap.toString());
        }
    }
}
当前堆顶为: 1     出栈后堆为SmallHeap{smallHeap=[3, 5, 4, 9, 97, 0, 0, 0, 0, 0]}
当前堆顶为: 3     出栈后堆为SmallHeap{smallHeap=[4, 5, 97, 9, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 4     出栈后堆为SmallHeap{smallHeap=[5, 9, 97, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5     出栈后堆为SmallHeap{smallHeap=[9, 97, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 9     出栈后堆为SmallHeap{smallHeap=[97, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 97     出栈后堆为SmallHeap{smallHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: -3     出栈后堆为SmallHeap{smallHeap=[1, 5, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 1     出栈后堆为SmallHeap{smallHeap=[5, 0, 0, 0, 0, 0, 0, 0, 0, 0]}
当前堆顶为: 5     出栈后堆为SmallHeap{smallHeap=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]}

堆的基础应用

用堆去解决这些问题不一定是最好的,但是一切都是为了锻炼使用堆的思维。

LeetCode剑指 Offer 40. 最小的k个数

维护一个大顶堆,大顶堆的大小为k,遍历数组,每次都将遍历到的元素与堆顶元素相比较,如果该元素小于顶堆元素则堆顶元素弹出,该元素进入堆中,维护堆结构。最后遍历输出堆。

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        BigHeap bigHeap = new BigHeap(k + 1);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
        for(int i = 0;i < arr.length;i++){
            bigHeap.push(arr[i]);
            if(bigHeap.size() > k) bigHeap.pop();
        }
        int[] nums = new int[k];
        for(int i = 0;i < k;i++){
            nums[i] = bigHeap.pop();
        }
        return nums;
    }
}
LeetCode1046. 最后一块石头的重量
class Solution {
    public int lastStoneWeight(int[] stones) {
        int len = stones.length;
        BigHeap bigHeap = new BigHeap(len);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
        for(int i : stones)
            bigHeap.push(i);
        while(bigHeap.size > 1){
            int x = bigHeap.pop();
            int y = bigHeap.pop();
            if(x == y) continue;
            if(x > y) bigHeap.push(x - y);
            else bigHeap.push(y - x);
        }
        return bigHeap.pop();
    }
}
LeetCode703. 数据流中的第 K 大元素

维护一个小顶堆,大顶堆的大小为k,遍历数组,每次都将遍历到的元素与堆顶元素相比较,如果该元素大于顶堆元素则堆顶元素弹出,该元素进入堆中,维护堆结构。最后输出堆顶元素。(堆中为前k个最大值)

class KthLargest {
    SmallHeap smallHeap;//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
    int k;
    public KthLargest(int k, int[] nums) {
        this.smallHeap = new SmallHeap(k + 1);
        this.k = k;
        for(int i = 0;i < nums.length;i++){
            smallHeap.push(nums[i]);
            if(smallHeap.size() > k) smallHeap.pop();
        }
    }
    
    public int add(int val) {
        smallHeap.push(val);
        if(smallHeap.size() > k) smallHeap.pop();
        return smallHeap.top();
    }
}

LeetCode215. 数组中的第K个最大元素

与上一道题大同小异

class Solution {
    public int findKthLargest(int[] nums, int k) {
        SmallHeap smallHeap = new SmallHeap(k + 1);//要把堆的代码copy过来,在上面。太长了所以就不复制进来了
        for(int i : nums){
            smallHeap.push(i);
            if(smallHeap.size() > k) smallHeap.pop();
        }
        return smallHeap.top();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值