优先级队列(堆)的学习


前言

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。通常采用堆数据结构来实现。


一、优先级队列(堆)的概念

首先,在建堆之前我们需要了解到:
1.堆逻辑上是一颗完全二叉树;
2.堆物理上是保存在数组中的;
3.满足任意节点的值都大于其子树中节点的值,叫做大堆(大根堆或最大堆)
最大堆的堆顶是整个堆中的最大元素
4.满足任意节点的值都小于其子树中节点的值,叫做小堆(小根堆或最小堆)
最小堆的堆顶是整个堆中的最小元素
5.堆的基本左用:快速找到集合中的最值

二、优先级队列(堆)的实现

1.向下调整(左右子树必须是一个堆才能调整)

以建小堆为例

 //优先级队列-->堆  向下调整  (建小堆为例)
    public static void shiftDown(int[] array,int size,int index){
        int left = 2*index+1;
        while (left <size){  //因为是完全二叉树 直接判断左子树即可
            int min = left;
            int right = 2*index +2;
            if(right<size){
                if(array[right]<array[left]){  //让左右子树的最小值放入min
                    min = right;
                }
            }
            if(array[index]<=array[min]){ // 再让要比较的父节点 与最小的比较 大的话交换,
                                          //小的话就意味着这就是一个小堆 不用交换 ,直接break
                break;
            }
            int t = array[index];
            array[index] = array[min];
            array[min] = t;
            index = min;    //最后这个根节点调整完  要把index 指向被换的数  再看这个数是否要调整
            left = 2*index+1;  
        }
    }

或者下面 一样 只是换成了孩子节点

 public void adjustDown(int parent,int len) {
        int child = 2*parent+1;
        while (child < len) {
            if(child+1 < len && this.elem[child] < this.elem[child+1]) {
                child++;
            }
            //child 下标是孩子节点最大值的下标
            if(this.elem[child] > this.elem[parent]) {
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

2.向上调整(以建大堆为例)

//向上调整 (建大堆为例)
    public static void shiftUp(int[] array,int size,int index) {  //此时index 应该给 长度-1  一次向上调整
        while (index > 0) {
            int parent = (index - 1) / 2;  //找到要调整的父亲节点
            if (array[parent] >= array[index]) { 
                break;
            }
            int t = array[parent];
            array[parent] = array[index];
            array[index] = t;
            index = parent;
        }
    }

或者下面的child 就相当于上面的index

 public void adjustUp(int child) {
        int parent = (child-1)/2;
        while (child > 0) {
            if(this.elem[child] > this.elem[parent]) {
                int tmp = this.elem[child];
                this.elem[child] = this.elem[parent];
                this.elem[parent] = tmp;
                child = parent;
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }

3.建堆(以建小堆为例)

    //建堆
    public static  void createHeap(int[] array, int size){
        for (int i = (size-1-1)/2; i>=0; i++) {  //i从最后一个节点(index-1)的父亲节点((index-1)-1)/2开始
            shiftUp(array,size,i);//向上调整 (建小堆)
        }
    }

4.入队、出队、返回队首元素的操作

class  MyPriorityQueue{
    private int[] array = new  int[10];
    private int size = 0;
    public void offer(int a){  //入队操作  并调整
        array[size++] = a; //尾插法加入 
        shiftDown(array,size,size-1);
    }
    public int poll(){//出队  先把第一个和最后一个交换 再调整,然后同时size--
        int tmp = array[0];
        array[0] = array[--size];
        shiftDown(array,size,0);
        return tmp;
    }
    public int peek(){
        return array[0];
    }
}

相关练习

1.找到和最小的 k 对数字

/**
     * 给定两个以升序排列的整形数组 nums1 和 nums2, 以及一个整数 k。
     * k为返回的对数
     * 定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2。
     *
     * 找到和最小的 k 对数字 (u1,v1), (u2,v2) ... (uk,vk)。
     */
    class Solution {
        public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
            PriorityQueue<List<Integer>> queue = new PriorityQueue<>(k, (o1, o2)->{
                return (o2.get(0) + o2.get(1)) - (o1.get(0) + o1.get(1));
            });
            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 && nums1[i]+nums2[j] > queue.peek().get(0) + queue.peek().get(1)){
                        break;
                    }
                    if(queue.size() == k){
                        queue.poll();
                    }
                    List<Integer> pair = new ArrayList<>();
                    pair.add(nums1[i]);
                    pair.add(nums2[j]);
                    queue.add(pair);
                }
            }
            List<List<Integer>> res = new LinkedList<>();
            for(int i =0; i < k && !queue.isEmpty(); i++){
                res.add(queue.poll());
            }
            return res;
        }

2.石头的重量

 /**
         * 有一堆石头,每块石头的重量都是正整数。
         *
         * 每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。
         * 假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
         * 如果 x == y,那么两块石头都会被完全粉碎;
         * 如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
         * 最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。
         */
        public int lastStoneWeight(int[] stones) {
           PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(stones.length, new Comparator<Integer>() {
               @Override
               public int compare(Integer o1, Integer o2) {
                   return o2-o1;
               }
           });
            for (int i:stones) {
                priorityQueue.add(i);
            }
                while (priorityQueue.size() >=2){
                    int x = priorityQueue.poll();
                    int y = priorityQueue.poll();
                    if(x > y){
                        priorityQueue.offer(x-y);
                    }
                }
            return priorityQueue.size()==1? priorityQueue.poll():0;
        }
    }

总结

根据前面栈、队列的学习,优先级队列的学习并不是很困难,操作都很类似,只是储存的方式和删除的方式有些不同,而且加上二叉树的知识,就更容易理解,它是以二叉树形式储存,而且是安全二叉树,只要掌握这些,就不难理解这部分知识。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值