【数据结构】堆(向下调整、堆的建立、top-K问题、最后一块石头问题)

目录

一、堆及其概念

二、向下调整(向上调整)

三、堆的建立

四、优先级队列

五、例题

1.top-k问题

2.最后一块石头的重量


一、堆及其概念

        堆在逻辑上是一颗完全二叉树,在物理上保存于数组。满足任意结点的值都大于其子树中结点的值,叫做大堆,或者大根堆,或者最大堆反之,则是小堆,或者小根堆,或者最小堆。堆的基本作用是在频繁变动的集合中找到最值。

二、向下调整(向上调整)

        前提:调整的左右节点必须是堆。才能调整。

        基本思想:

1.判段index位置是否是叶子节点

2.找到该节点的两个孩子的最值(需要知道是否有孩子)

3.将最值和当前需要调整的位置进行比较,判断是否满足堆的性质

4.交换两个值

5.由于交换的原因,调整过后的节点可能不满足堆的性质,继续调整

    public static void shiftDown(long[] array, int size, int index) {
        // 只要看到 int 类型的,基本就是下标或者个数,不是元素

        // 这里直接 while(true)即可
        // while (2 * index + 1 < size) {    如果这么写,下面就不用再进行叶子的判断
        while (true) {
            // 1. 判断 index 所在位置是不是叶子
            // 逻辑上,没有左孩子一定就是叶子了(因为完全二叉树这个前提)
            int left = 2 * index + 1;
            if (left >= size) {
                // 越界 -> 没有左孩子 -> 是叶子 -> 调整结束
                return; // 循环的出口一:走到的叶子的位置
            }

            // 2. 找到两个孩子中的最值【最小值 via 小堆】
            // 先判断有没有右孩子
            int right = left + 1;       // right = 2 * index + 2
            int min = left;             // 假设最小值就是左孩子,所以 min 保存的最小值孩子所在的下标
            if (right < size && array[right] < array[left]) {
                // right < size 必须在 array[right] < array[left] 之前,不能交换顺序
                // 因为先得确定有右孩子,才有比较左右孩子的意义
                // 有右孩子为前提的情况下,然后右孩子的值 < 左孩子的值
                min = right;            // min 应该是右孩子所在的下标
            }

            // 3. 将最值和当前要调整的位置进行比较,判断是否满足堆的性质
            if (array[index] <= array[min]) {
                // 当前要调整的结点的值 <= 最小的孩子值;说明这里也满足堆的性质了,所以,调整结束
                return; // 循环的出口一:循环期间,已经满足堆的性质了
            }

            // 4. 交换两个值,物理上对应的就是数组的元素交换 min 下标的值、index 下标的值
            long t = array[index];
            array[index] = array[min];
            array[min] = t;

            // 5. 再对 min 位置重新进行同样的操作(对 min 位置进行向下调整操作)
            index = min;
        }
    }

向上调整后续的例子中介绍

三、堆的建立

        我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。

        基本思路:从最后一个非叶子节点开始,逐步往前进行进行向下调整。代码如下

    public static void createHeap(long[] array, int size) {
        // 从最后一个非叶子结点的双亲开始
        // 最后一个结点的下标一定是: size - 1
        // 它的双亲一定是: ((size - 1) - 1) / 2 = (size - 2) / 2
        // 从后往前遍历,直到根也被向下调整过
        // [(size - 2) / 2, 0]  左闭右闭
        for (int i = (size - 2) / 2; i >= 0; i--) {
            shiftDown(array, size, i);
        }
    }

其中shiftDown操作为向下调整。

四、优先级队列

        我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次 高的对象。在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这 种数据结构就是优先级队列(Priority Queue)。优先级队列的实现方式有很多,但最常见的是使用堆来构建。

        我们优先级队列的具体使用方式如下


import java.util.Comparator;
import java.util.PriorityQueue;

public class Demo3 {
    public static void main(String[] args) {
/*
        PriorityQueue<Integer> pq=new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return -o1.compareTo(o2);
            }
        });
*/
        PriorityQueue<Integer> pq=new PriorityQueue<>(((o1, o2) ->-o1.compareTo(o2)));

        pq.offer(1);
        pq.offer(2);
        pq.offer(3);
        pq.offer(4);
        pq.offer(5);

        Integer peek = pq.peek();
        System.out.println(peek);
        System.out.println("=========================================");
        while (!pq.isEmpty()){
            Integer poll = pq.poll();
            System.out.println(poll);
        }
    }
}

        我们重写了比较器,这样就可以按照我们的要求来进行操作。具体采用了两种,一种是匿名类的写法,还有一种是lambda表达式。

五、例题

1.top-k问题

题目链接

思路:这题的解法较多,不在此处赘述。我们采用堆的方法来处理。并且为了巩固练习向下调整等操作。

1.将数组的前K个元素建立成为一个大堆

2.从K+1个元素开始,与根节点进行比较,如果比根节点小,那么我们用这个值替换掉根节点。然后进行调整。

3.循环完成后输出这个堆,此时得到的就是最小的K个数。

import java.util.*;
class Solution {
    public void adjust_heap(int[] heap,int t){
        if(heap[0]>t){
            heap[0]=t;
            shiftDown(heap,heap.length,0);
        }
    }

    public int[] smallestK(int[] arr, int k) {
        if(arr.length==0||k==0){
            return new int[0];
        }
        int[] heap=makeHeap(arr,k);
        for(int i=k;i<arr.length;i++){
            adjust_heap(heap,arr[i]);
        }
        return heap;
    }
    public int[] makeHeap(int[] arr,int k){
        int[] list=Arrays.copyOf(arr,k);
        for(int i=(k-2)/2;i>=0;i--){
            shiftDown(list,k,i);
        }
        return list;
    }
    public void shiftDown(int[] arr,int size,int i){
        while(true){
            int left=2*i+1;
            if(left>=size){
                return;
            }
            int right =left+1;
            int max=left;
            if(right<size&&arr[right]>arr[left]){
                max=right;
            }

            if(arr[i]>=arr[max]){
                return;
            }

            int t =arr[i];
            arr[i]=arr[max];
            arr[max]=t;

            i=max;
        }
    }
}

2.最后一块石头的重量

题目链接

思路:目的是为了练习优先级队列。所以我们采用优先级队列将重量全部接收。然后抛出最大的两个值,如果这两个值不相等,我们就再接收它们的差值。

class Solution {
    public int lastStoneWeight(int[] stones) {
        PriorityQueue<Integer> pq=new PriorityQueue<>(((o1, o2) -> o2-o1));
        for (int stone : stones) {
            pq.offer(stone);
        }
        while(pq.size()>1){
            int a=pq.poll();
            int b=pq.poll();
            if(a>b){
                pq.offer(a-b);
            }
        }
        return pq.isEmpty() ? 0:pq.poll();
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值