【Java--数据结构】堆(优先级队列)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗~

如有错误,欢迎指出~



目录

堆的性质

测试堆

模拟实现堆 

创建堆

堆的插入

堆的删除  

 堆的使用

topK 问题

topK的简单解法

解法2(减少了时间复杂度) 

堆排序 


堆 提供两个最基本的操作: 返回最高优先级对象 和 添加新的对象。

堆是一个完全二叉树,由顺序存储的(数组模拟) ,有大根堆和小根堆两种堆

将根节点最大的堆叫做 最大堆 或大根堆,根节点最小的堆叫做最小堆或小根堆。

堆的性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树。

已知父节点为i 孩子节点分别为2i+1和2i+2

已知孩子节点为i 父节点为(i-1)/2

测试堆

    public static void main(String[] args) {
        PriorityQueue<Integer> priorityQueue=new PriorityQueue<>();
        priorityQueue.offer(12);//默认创建的是一个小根堆
        priorityQueue.offer(5);
        priorityQueue.offer(57);

        System.out.println(priorityQueue.poll());//5
        System.out.println(priorityQueue.poll());//12
    }

模拟实现堆 

创建堆

public class MyHeap {
    public int[] elem;
    public int usedSize;

    public MyHeap() {
        this.elem = new int[10];
    }

    //初始化
    public void init(int[] array) {
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
    }

将堆调整为大根堆

 从堆的最后一颗子树开始调整

左右子树的最大值与根节点比较,大于根节点的就与根交换.

p  ->(len-1-1)/2

child  ->2*p+1p

时间复杂度:T(n)=n-log(n+1),当n增大时,约等于n

因此,建堆的时间复杂度为:O(n)

//调整为大根堆
    public void createHeap() {
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
            siftDown(parent, usedSize);//usedSize是向下调整结束的条件
        }
    }

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

    //向下调整
    public void siftDown(int parent, int end) {
        int child = 2 * parent + 1;
        while (child < end) {
            //比较child和child+1的值,使child 的值为最大
            if (child + 1 < end && elem[child] < elem[child + 1]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                //继续往下调整
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

堆的插入

堆尾插入一个数,并自动调整为大根堆

    //插入一个数,并自动调整为大根堆
    public void offer(int val){
        if(isFull()){
            elem= Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize]=val;
        usedSize++;
        siftUp(usedSize-1);//usedSize-1是新插入的值的下标
    }

    public void siftUp(int child){
        int parent=(child-1)/2;
        while(parent>=0){
            if(elem[child]>elem[parent]){
                swap(child,parent);
                child=parent;
                parent=(child-1)/2;
            }else{
                break;
            }
        }
    }
    public boolean isFull(){
        return usedSize==elem.length;
    }

堆的删除  

堆的删除一定删除的是堆顶元素。

具体如下:

1. 将堆顶元素对堆中最后一个元素交换

2. 将堆中有效数据个数减少一个

3. 对堆顶元素进行向下调整  

    //删除一个数
    public int poll(){
        if(isEmpty()){
            return -1;
        }
        int old=elem[0];
        swap(0,usedSize-1);
        usedSize--;
        siftDown(0,usedSize);
        return old;
    }

    public boolean isEmpty(){
        return usedSize==0;
    }

 堆的使用

1.使用时必须导入PriorityQueue所在的包

2. PriorityQueue中放置的元素必须要能够比较大小不能插入无法比较大小的对象,否则会抛出 ClassCastException异常

3. 不能插入null对象,否则会抛出NullPointerException

4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容

5. 插入和删除元素的时间复杂度为O(logN)

6. PriorityQueue底层使用了数据结构

7. PriorityQueue默认情况下是小堆---即每次获取到的元素都是最小的元素

topK 问题

oj链接

 最 大 或 者 最 小 的 前 k 个 数 据 。

topK的简单解法

求最小的前K个数据

时间复杂度:O((N+K)*logN)

   
    public static int[] smallestK(int[] arr,int k){
        PriorityQueue<Integer> minHeap=new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            minHeap.offer(arr[i]);
        }
        int[] tmp=new int[k];
        for (int i = 0; i < k; i++) {
            int val=minHeap.poll();
            tmp[i]=val;
        }
        return tmp;
    }

解法2(减少了时间复杂度) 

时间复杂度:O(N*logk)

求最小的前K个数据

现将前k个元素建立大小为K的大根堆

用i遍历剩下的元素

堆顶元素大于当前i 下标的值就出堆

//比较器,将堆的默认值从小根堆改为大根堆
class IntCmp implements Comparator<Integer> {
    public int compare(Integer o1,Integer o2){
        return o2.compareTo(o1);
    }
}

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] tmp=new int[k];
        if(k==0){
            return tmp;
        }
        PriorityQueue<Integer> maxHeap=new PriorityQueue<>(new IntCmp());
        //将前k个元素放入堆中
        for(int i=0;i<k;i++){
            maxHeap.offer(arr[i]);
        }
        //遍历剩下的n-k个元素
        for (int i = k; i < arr.length; i++) {
            if(arr[i]<maxHeap.peek()){
                maxHeap.poll();
                maxHeap.offer(arr[i]);
            }
        }
        for (int i = 0; i < k; i++) {
            int val=maxHeap.poll();
            tmp[i]=val;
        }
        return tmp;
    }
}

堆排序 

从小到大排序

要求数组本身排序,不能申请额外的内存

建立一个大根堆,将0下标与最后一个交换,

调整(不包括刚刚交换的元素)

时间复杂度:O(N*(logN))

    public void heapSort(){
        int endIndex=usedSize-1;
        while(endIndex>0){
            swap(0,endIndex);
            siftDown(0,endIndex);
            endIndex--;
        }
    }

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值