堆和优先队列

1.概念

(二叉)堆是一个数组,它可以看成一个近似的完全二叉树,堆有大根堆和小根堆两种,大根堆就是父节点大于其左右孩子节点,每个节点都满足如此规则构建成的一种数据结构(小根堆则相反),可以见下面图片:
在这里插入图片描述

2.相关操作

  • 建堆:建堆的过程是一个自底向上的过程
    在这里插入图片描述

  • 调整堆
    在这里插入图片描述

3.Java代码实现

将传入的数组构建成一个大根堆(不使用PriorityQueue的实现)

// 原地堆化(最大堆)
// 堆化可以保证 h[0] 是堆顶元素,且 h[i] >= max(h[2*i+1], h[2*i+2])
private void heapify(int[] h) {
    // 倒着遍历,从而保证 i 的左右子树一定是堆,那么 sink(h, i) 就可以把左右子树合并成一个堆
    // 下标 >= h.length / 2 的元素是二叉树的叶子,无需下沉
    for (int i = h.length / 2 - 1; i >= 0; i--) {
        sink(h, i);
    }
}

// 把 h[i] 不断下沉,每次找左右儿子中最大的交换,直到 i 的左右儿子都 <= h[i] 时停止
private void sink(int[] h, int i) {
    int n = h.length;
    while (2 * i + 1 < n) {
        int j = 2 * i + 1; // i 的左儿子
        if (j + 1 < n && h[j + 1] > h[j]) { // i 的右儿子比 i 的左儿子大
            j++;
        }
        if (h[j] <= h[i]) { // 说明 i 的左右儿子都 <= h[i],停止下沉
            break;
        }
        swap(h, i, j); // 下沉
        i = j;
    }
}

// 交换 h[i] 和 h[j]
private void swap(int[] h, int i, int j) {
    int tmp = h[i];
    h[i] = h[j];
    h[j] = tmp;
}

4.堆的应用

4.1堆排序

堆排序的实现过程就是每一次将堆顶元素与堆最末尾的元素交换,然后堆的size-1(也就是最后一个元素排除出去了),然后调整堆,如此重复n-1次,最终得到一个有序数组

void HeapSort(ElemType A[], int len){
	BuildMaxHeap(A, len);//建堆
	for(i = len; i > 1; i--){
		Swap(A[i], A[1]);//交换元素
		HeadAdjust(A, 1, i-1);//调整堆
	}
}

4.2优先队列

首先,我们要明白二叉堆是实现优先队列的一种底层结构。

PriorityQueue,即优先级队列。优先级队列可以保证每次取出来的元素都是队列中的最小或最大的元素(Java优先级队列默认每次取出来的为最小元素)。JDK1.8中的PriorityQueue底层使用了堆这种数据结构。

在这里插入图片描述
优先队列的相关操作详见下面文章(具体的操作可以查看Java API文档)

https://blog.csdn.net/LEE180501/article/details/128728900

在这里插入图片描述

4.3Java中优先队列的使用案例

我们以下面leetcode中的题目为使用场景,熟悉一下优先队列的使用。在本题的求解过程中,要取出队列的最大值进行操作之后,再将操作后的结果放回队列中,队列还需要保持有序性。
在这里插入图片描述
所以,我们在实现题解的过程了,用lambda表达式定义了一个比较器,这个比较器用于决定队列中元素的优先级。(不定义比较器的化,默认的就是小根堆,这里就是让元素值大的优先级高)
(a, b) -> b - a: 这是一个lambda表达式,它定义了一个比较器(comparator)。

a 和 b 是队列中待比较的两个元素。
-> 是lambda操作符,用于定义一个箭头函数。
b - a 是该函数的返回值,表示如果 b 的优先级高于 a,则返回一个正数;如果 a 的优先级高于 b,则返回一个负数;如果两者相等,则返回0。
class Solution {
    public long pickGifts(int[] gifts, int k) {
        //优先队列
        PriorityQueue<Integer> pq = new PriorityQueue<>((a,b)->b -a);
        for(int gift : gifts){
            pq.offer(gift);
        }
        while(k > 0){
            k--;
            int x = pq.poll();
            pq.offer((int) Math.sqrt(x));
        }
        long res = 0;
        while(!pq.isEmpty()){
            res += pq.poll();
        }
        return res;
    }
}

5.C++、Python中堆的使用

以如下leetcode题目的应用场景为例,讲述C++、python中堆的使用。
在这里插入图片描述

5.1Python中的使用

默认是小根堆

class Solution:
    def minStoneSum(self, piles: List[int], k: int) -> int:
        for i in range(len(piles)):
            piles[i] *= -1  # 变成相反数,这样堆化后就是最大堆了
        heapify(piles)  # 原地堆化
        while k and piles[0] != -1:
            heapreplace(piles, piles[0] // 2)  # 负数下取整等于正数上取整
            k -= 1
        return -sum(piles)

5.2C++中的使用

默认初始化为大根堆

class Solution {
public:
    int minStoneSum(vector<int> &piles, int k) {
        make_heap(piles.begin(), piles.end()); // 原地堆化(最大堆)
        while (k-- && piles[0] != 1) {
            pop_heap(piles.begin(), piles.end()); // 弹出堆顶并移到末尾
            piles.back() -= piles.back() / 2;
            push_heap(piles.begin(), piles.end()); // 把末尾元素入堆
        }
        return accumulate(piles.begin(), piles.end(), 0);
    }
};


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值