优先级队列(堆)

优先级队列(堆)

文章目录

  • 优先级队列(堆)
    • 1.二叉树的存储顺序
    • 2.1操作—向下调整:
    • 2.2建堆的时间复杂度分析
    • **3.** 堆的应用:优先级队列
      • **3.1** **概念**
      • **3.2** **内部原理**
      • **3.3** **操作**-入队列
      • **3.4** 操作-出队列(优先级最高)
      • 3.5 **返回队首元素(优先级最高)**
    • **4.** **堆的其他应用**-TopK **问题**
    • 5.面试题
    • **6.** 堆的其他应用-堆排序
  • **java**对象的比较
      • 集合框架中PriorityQueue的比较方式
      • 模拟实现PriorityQueue

  • 堆的概念及实现
  • 掌握PriorityQueue的使用

1.二叉树的存储顺序

1.1 使用数组保存二叉树结构,通过层序便利的方式去将二叉树的每个节点放入数组中。

而且用于完全二叉树,因为非完全二叉树在数组中存储会有空间的浪费,这种方式就是用堆表示

优先级队列(堆)_第1张图片

2.知道双亲或者孩子节点的下标,可以求出另一个的下标,在下面实现堆的代码时有用。

3.小根堆和大根堆:

优先级队列(堆)_第2张图片

4.堆的概念:

  1. 堆逻辑上是一棵完全二叉树

  2. 堆物理上是保存在数组中
    优先级队列(堆)_第3张图片

5.堆的基本作用是,快速找集合中的最值


2.1操作—向下调整:

前提:左右子树必须已经是一个堆,才能调整

优先级队列(堆)_第4张图片

向下调整的代码:

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

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

    public void swap(int child,int parent){
        int tmp = elem[child];
        elem[child] = elem[parent];
        elem[parent] = tmp;
    }

    public void shiftDown(int parent,int len){
        int child = parent*2+1;
        while (child<len){ //保证了,每次向下检测后,child越界了,那么此次的检测不需要了,praent开始重新传参,但是len不变,因为你每次都要去向下检测,一旦
            //越界,表明检测结束
            if(child+1<len && elem[child]<elem[child+1]){
                child = child+1;
            }
            if(elem[child]>elem[parent]) {
                swap(child, parent);
                //当孩子和双亲交换后,我们开始向下检测去重新赋值
                parent = child;
                child = parent*2+1;
            }else{
                //不需要交换的时候,表明此树满足,那么没有交换,下面本来就已经调整完了,之所以向下检测是因为你上面的书发生了交换可能导致下面的树不满足了。
                //所以直接break结束本次循环,继续另一棵树
                break;
            }
        }
    }


    //创建一个堆,等会进行向下调整
    public void creatHeap(int[] array){
        //这里是把传入的数组进行接收
        for (int i = 0; i < array.length; i++) {
            elem[i] = array[i];
            usedSize++;
        }
        //根据代码显示  时间复杂度为nlogn  实际上是n,具体可以看板书
        for (int parent = (usedSize-1-1)/2; parent >=0 ; parent--) {
            shiftDown(parent,usedSize);//这里传的usedSize 而不是array.length 需要的是传过来的元素,可能array后面很多都是0
        }
    }
}

2.2建堆的时间复杂度分析

最坏的情况即图示的情况,从根一路比较到叶子,比较的次数为完全二叉树的高度

优先级队列(堆)_第5张图片


3. 堆的应用:优先级队列

3.1 概念

在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次

高的对象。最简单的一个例子就是,在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。

在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这

种数据结构就是优先级队列(Priority Queue)

3.2 内部原理

优先级队列的实现方式有很多,但最常见的是使用堆来构建。

3.3 操作-入队列

过程(以大堆为例):

  1. 首先按尾插方式放入数组

  2. 比较其和其双亲的值的大小,如果双亲的值大,则满足堆的性质,插入结束

  3. 否则,交换其和双亲位置的值,重新进行 2、3 步骤

  4. 直到根结点

//向上调整:入队列
    private void shiftup(int child){
        int parent = (child-1)/2;
        while(child>0){
            if(elem[child]>elem[parent]){
                swap(child,parent);
            }
            child = parent;
            parent = (child-1)/2;
        }
    }
    public void offer(int val){
        if(isFull()){
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }
        elem[usedSize++] = val;
        shiftup(usedSize-1);
    }
    public boolean isFull(){
        return usedSize == elem.length;
    }

3.4 操作-出队列(优先级最高)

3.5 返回队首元素(优先级最高)

删除堆顶元素

返回堆顶元素即可

为了防止破坏堆的结构,删除时并不是直接将堆顶元素删除,而是用数组的最后一个元素替换堆顶元素,然后通过向

下调整方式重新调整成堆

优先级队列(堆)_第6张图片

 //出队列:最后一个元素与0下标进行交换,然后usedSize--即可
    public int poll(){
        if(isEmpty()){
            throw new RuntimeException("优先级队列为空!");
        }
        int tmp = elem[0];
        elem[0] = elem[usedSize-1];
        elem[usedSize-1] = tmp;
        usedSize--;
        shiftDown(0,usedSize);
        return tmp;
    }
    public boolean isEmpty(){
        return usedSize == 0;
    }
    public int peek(){
        if(isEmpty()){
            throw new RuntimeException("优先级队列为空!");
        }
        return elem[0];
    }

4. 堆的其他应用-TopK 问题

优先级队列(堆)_第7张图片

总结:如果求前k个最大的元素,就要建小根堆

如果求前k个最小的元素,就要建大根堆

如果求第k个最大的元素,就要建小根堆,堆顶元素就是第k大的元素

如果求第k个最小的元素,就要建大根堆,堆顶元素就是第k小的元素

TopK的代码,有一题还是关于找出和最小的k对数字
public class TopK {

    public static int[] topK(int[] array,int k){
        //创建一个大根堆
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (int i = 0; i < array.length; i++) {
            if(maxHeap.size() < k ){
                maxHeap.offer(array[i]);
            }else{
                int tmp = maxHeap.peek();
                if(tmp > array[i]){
                    maxHeap.poll();
                    maxHeap.offer(array[i]);
                }
            }
        }
        int[] tmp = new int[k];
        for (int i = 0; i < tmp.length; i++) {
            tmp[i] = maxHeap.poll();
        }
        return tmp;
    }
    public static void main(String[] args) {
        int[] array = {18,21,8,10,34,12};
        int[] tmp = topK(array,3);
        System.out.println(Arrays.toString(tmp));
    }
}

5.面试题

//力扣373.查找和最小的k对数字:也是利用TopK解决
    public static List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {

        PriorityQueue<List<Integer>> maxHeap = new PriorityQueue<>(k, new Comparator<List<Integer>>() {
            @Override
            public int compare(List<Integer> o1, List<Integer> 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(maxHeap.size() < k){
                    List<Integer> tmpList = new ArrayList<>();
                    tmpList.add(nums1[i]);
                    tmpList.add(nums2[j]);
                    maxHeap.offer(tmpList);
                }else{
                    int top = maxHeap.peek().get(0)+maxHeap.peek().get(1);
                    if(top > nums1[i]+nums2[j]){
                        //弹出
                        maxHeap.poll();

                        List<Integer> tmpList = new ArrayList<>();
                        tmpList.add(nums1[i]);
                        tmpList.add(nums2[j]);
                        maxHeap.offer(tmpList);
                    }
                }
            }
        }
        List<List<Integer>> ret = new ArrayList<>();
        for (int i = 0; i < k && !maxHeap.isEmpty(); i++) { //这里还需要加个条件:!maxHeap不为空,有一种情况是:
            //你本身就是给的两个数组,只能组成的对数比k还小,那么你就只能把有的都出了,痛出不了k对数,所以最后堆为空了就不能出了
            ret.add(maxHeap.poll());
        }
        return  ret;
    }

6. 堆的其他应用-堆排序

对一组数据进行从小到大排序:

优先级队列(堆)_第8张图片

1.调整完大根堆

2.将0下标和最后一个元素进行交换,然后进行调整:每次将数组最大的元素放在后面,便可以从小到大排序

3.不断循环,end–(end = 0结束)****

public void heapSort(){
        int end = usedSize-1;
        while(end>0){
            swap(0,end);
            shiftDown(0,end);//这里传的是useSize-1 而不是usedSize ,在原本的函数里传的是不减1,但此时你每循环一次,你的最后一个元素都确定了,
            //此时你调整的这棵树就是0(因为你当时把堆顶换了)作为双亲,然后你结束的位置是变的,第二次就是去确定倒数第二个元素,每次都要减1
            end--;
        }
    }

java对象的比较

  • 问题的提出
  • 元素的比较
  • Java中对象的比较
  • 集合框架中PriorityQueue的比较方式
  • 模拟实现PriorityQueue

上节课我们讲了优先级队列,优先级队列在插入元素时有个要求:插入的元素不能是null或者元素之间必须要能够

进行比较,为了简单起见,我们只是插入了Integer类型,那优先级队列中能否插入自定义类型对象呢?

<优先级队列(堆)_第9张图片


集合框架中PriorityQueue的比较方式

优先级队列(堆)_第10张图片


模拟实现PriorityQueue

/*class Card implements Comparable{
    public int rank; // 数值
    public String suit; // 花色
    public Card(int rank, String suit) {
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public int compareTo(Card o) {
        return  this.rank - o.rank  ;//继承了比较接口,此时实现抽象方法再去指定比较的属性,对类的侵入性较高,一旦写死了某种方式比较,就不能轻易进行修改
        // 可以考虑采用比较器来比较
    }
    
     //这里重写equals方法,当你想要拿equals方法去比较时,由于Card没有重写,会默认调用Obiect中的该方法,他比较的是地址,所以你这里需要重写一下
    //比较对象的内容是否相同,你需要重新重写一下
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;//是不是地址相同,同一个引用
        //getClass() != o.getClass() 比较的是:是不是同一个类型
        if (o == null || getClass() != o.getClass()) return false;
        Card card = (Card) o;
        return rank == card.rank && Objects.equals(suit, card.suit);//比较内容
    }

    @Override
    public String toString() {
        return "Card{" +
                "rank=" + rank +
                ", suit='" + suit + '\'' +
                '}';
    }
}*/

比较器来实现比较
 class RankComparator implements Comparator {

    @Override
    public int compare(Card o1, Card o2) {
        return o1.rank-o2.rank;
    }
}

public class TestDemo {
    public static void main(String[] args) {
          PriorityQueue cards = new PriorityQueue<>();
        Card card1 = new Card(1,"♥");
        Card card2 = new Card(2,"♥");
        RankComparator rankComparator = new RankComparator();
        PriorityQueue priorityQueue = new PriorityQueue<>(rankComparator);//这里你需要传比较器,指定比较器的比较方式没否则你是默认比较,
        //但是你又没有指定属性去比较,就会报错
        //这种写法也是可以,内部类的写法
        /*PriorityQueue priorityQueue = new PriorityQueue<>(new Comparator() {
            @Override
            public int compare(Card o1, Card o2) {
                return o1.rank-o2.rank;
            }
        });*/
        priorityQueue.offer(card1);//直接放到底层的queue数组的0下标
        priorityQueue.offer(card2);
        System.out.println(priorityQueue);
}

优先级队列(堆)_第11张图片


你可能感兴趣的:(leetcode,算法,java)