堆的几种应用

目录

一、优先级队列

​二、TopK问题

2.1 面试题 17.14.最小K个数

2.2 Num347.前k个高频元素

2.3 Num373.查找和最小的k对数

三、堆排序 


一、优先级队列

优先级队列:底层基于堆的队列,可以按照元素间优先级的大小动态顺序出队。

基于最大堆的优先级队列:

import queue.Queue;
/**
 * 基于最大堆的优先级队列实现,值越大优先级越高
 * 队首元素就是优先级最大的元素
 */
public class PriorityQueue implements Queue<Integer> {
    private MaxHeap heap;
    public PriorityQueue(){
        heap=new MaxHeap();
    }
    @Override
    public void offer(Integer val) {
        heap.addNode(val);
    }
    @Override
    public Integer poll() {
        return heap.extractMax();
    }
    @Override
    public Integer peek() {
        return heap.peekMax();
    }
    @Override
    public boolean isEmpty() {
        return heap.isEmpty();
    }
}

但是JDK的优先级队列默认是最小堆,要将最小堆改为最大堆就需要知道元素间是怎么比较大小的。

元素间大小比较:

1、实现Comparable接口:

升序排列

 降序排列

2、传入java.util.Comparator -> 比较器 :

使用Comparator将JDK的最小堆改为最大堆:

二、TopK问题

TopK问题:从一堆数据中选出前**多少个数——应用堆来解决问题,取大用小,取小用大

2.1 面试题 17.14.最小K个数

题目描述如下:

解题思路:要找出数组中最小的k个数,就要构造一个有k个元素的最大堆,最大堆的堆顶元素值最大,扫描数组入队,直到队列中元素为k个,然后比较堆顶元素和扫描的元素,如果堆顶元素小于扫描元素,继续扫描,如果堆顶元素大于扫描元素 ,就将堆顶元素出队,扫描元素入队,不断“打擂”,将更小的元素换到堆中。

代码如下:

import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
/**
 * 最小K个数
 */
public class Offer17_14_SmallestK {
    public int[] smallestK(int[] arr, int k) {
        if (arr.length==0||k==0){
            return new int[0];
        }
        //JDK默认是最小堆,需要使用比较器来让它变成最大堆
        Queue<Integer> queue=new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (int i:arr){
            //当堆中元素个数小于k时,入队
            if (queue.size()<k){
                queue.offer(i);
            }else{
                //否则比较队首元素和i的大小,此时i更大则继续比较,
                // i小于队首元素时,将队首出队,然后将i入队
                int x=queue.peek();
                if (i>x){
                    continue;
                }else{
                    queue.poll();
                    queue.offer(i);
                }
            }
        }
        int[] ret=new int[k];
        for (int i = 0; i < ret.length; i++) {
            ret[i]=queue.poll();
        }
        return ret;
    }
}

2.2 Num347.前k个高频元素

题目描述如下:

解题思路:本题要求返回出现频次前k高的元素,所以需要比较元素出现的次数,首先将数组中出现的元素以及它得出现次数存到Map中,然后扫描Map集合,将最小堆依次出队即得到了答案,定义一个类,来存放数组中的每个元素以及它出现的次数,会大大提高解题效率 。

代码如下:

import java.util.*;
/**
 * 前K个高频元素
 * 示例 1:
 * 输入: nums = [1,1,1,2,2,3], k = 2
 * 输出: [1,2]
 */
public class Num347_TopKFrequent {
    private class Freq{
        int key;
        int times;
        public Freq(int key, int times) {
            this.key = key;
            this.times = times;
        }
    }
    public int[] topKFrequent(int[] nums, int k) {
        int[] ret=new int[k];
        //1.遍历数组,将数组中每个元素和它出现的次数存进map集合中
        Map<Integer,Integer> map=new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            //此时map中还没有存入nums[i]
            if (!map.containsKey(nums[i])){
                map.put(nums[i],1);
            }else{
                //此时nums[i]已经存过了
                map.put(nums[i],map.get(nums[i])+1);
            }
        }
        Queue<Freq> queue=new PriorityQueue<>(new Comparator<Freq>() {
            @Override
            public int compare(Freq o1, Freq o2) {
                return o1.times-o2.times;
            }
        });
        //2.遍历map,将当前出现频次最高的前k个数入队
        for (Map.Entry<Integer,Integer> entry:map.entrySet()){
            if (queue.size()<k){
                queue.offer(new Freq(entry.getKey(),entry.getValue()));
            }else{
                Freq freq=queue.peek();
                if (freq.times> entry.getValue()){
                    continue;
                }else{
                    queue.poll();
                    queue.offer(new Freq(entry.getKey(),entry.getValue()));
                }
            }
        }
        for (int i = 0; i < k; i++) {
            ret[i]=queue.poll().key;
        }
        return ret;
    }
}

2.3 Num373.查找和最小的k对数

题目描述如下:

特殊情况:当k大于数组长度时

解题思路:使用最大堆来解决问题,定义一个类Pair,将两个数组中的元素分别存进去,然后遍历两个数组,比较元素和谁大谁小,最后将最大堆出队即可。

代码如下:

import java.util.*;
/**
 * 查找和最小的K对数字
 */
public class Num373_KSmallestPairs {
    private class Pair implements Comparable<Pair>{
        int u;
        int v;
        public Pair(int u, int v) {
            this.u = u;
            this.v = v;
        }
       //实现Comparable接口将最小堆改造为最大堆
        @Override
        public int compareTo(Pair o) {
            return (o.u+o.v)-(this.u+this.v);
        }
    }
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        Queue<Pair> queue=new PriorityQueue<>();
        //遍历两个数组,u来自第一个数组,v来自第二个数组
        //循环终止条件Math.min(nums1.length,k),其中k可能大于数组长度
        for (int i = 0; i < Math.min(nums1.length,k); i++) {
            for (int j = 0; j < Math.min(nums2.length,k); j++) {
                if (queue.size()<k){
                    queue.offer(new Pair(nums1[i],nums2[j]));
                }else{
                    int sum=nums1[i]+nums2[j];
                    Pair peek=queue.peek();
                    if ((peek.u+peek.v)<sum){
                        continue;
                    }else{
                        queue.poll();
                        queue.offer(new Pair(nums1[i],nums2[j]));
                    }
                }
            }
        }
        List<List<Integer>> ret=new ArrayList<>();
        //此时i不仅要小于k,队列还不能为空
        for (int i = 0; i < k&&(!queue.isEmpty()); i++) {
            List<Integer> list=new ArrayList<>();
            Pair pair=queue.poll();
            list.add(pair.u);
            list.add(pair.v);
            ret.add(list);
        }
        return ret;
    }
}

三、堆排序 

堆排序:

  1. 将数组调整为最大堆;
  2. 然后交换arr[0]和最后一个元素,此时最后一个元素就是数组中的最大值;
  3. 从0到倒数第二元素开始进行siftDown操作;

代码如下:

    /**
     * 堆排
     * @param arr
     */
    public static void heapSort(int[] arr){
        //先将arr调整为最大堆,从最后一个非叶子节点开始进行siftDown操作
        for (int i = (arr.length-1-1)/2; i >=0 ; i--) {
            siftDown(arr,i,arr.length);
        }
        //此时arr已经被调整为最大堆
        for (int i = arr.length-1; i >0 ; i--) {
            //交换arr[0]和最后一个元素
            swap(arr,0,i);
            //然后从0到倒数第二个元素开始进行下沉操作
            siftDown(arr,0,i);
        }
        System.out.println(Arrays.toString(arr));
    }
    /**
     *元素下沉操作
     * @param arr
     * @param i
     * @param length
     */
    private static void siftDown(int[] arr, int i, int length) {
        //当前节点存在左子树
        while(2*i+1<length){
            //此时j为左子树节点
            int j=2*i+1;
            //如果当前节点存在右子树并且右子树的值大于左子树的值
            if ((j+1)<length&&arr[j+1]>arr[j]){
                //此时j为右子树节点
                j=j+1;
            }
            //比较当前节点值与其左右子树值的大小
            if (arr[i]>arr[j]){
                break;
            }else{
                swap(arr,i,j);
                i=j;
            }
        }
    }
    //交换
    private static void swap(int[] arr, int i, int j) {
        int tmp=arr[i];
        arr[i]=arr[j];
        arr[j]=tmp;
    }
    public static void main(String[] args) {
        int[] arr={3,6,2,5,9,12,90,24,1};
        heapSort(arr);
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

怎样让大排不硬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值