优先级队列-堆数据结构

优先级队列(堆)

  • 优先级队列( PriorityQueue)
      • 优先级队列的概念
      • 优先级队列的使用
          • 1. 三种构造方式
          • 2. 优先级队列中常用方法
          • 3. 使用时的注意事项
  • 优先级队列的模拟实现---->堆
      • 堆的概念
      • 堆的顺序存储
      • 堆的创建
      • 使用堆模拟实现优先级队列
      • 堆的应用

优先级队列( PriorityQueue)

优先级队列的概念

我们知道队列是先进先出的结构,而优先级队列则是指元素进出顺序是有先后次序的,在出队列时,可能需要优先级高或者优先级低的元素先出队列,这种数据结构就称为优先级队列;该结构是线程不安全的;
类似于医院,银行,机场或者其他地方一般设有的“军人优先”的情况;

在集合框架中所处的位置

箭头代表继承关系

优先级队列-堆数据结构_第1张图片

优先级队列的使用

1. 三种构造方式

优先级队列-堆数据结构_第2张图片

  • 创建空优先级队列
 //创建空优先级队列---默认容量为11
 PriorityQueue q=new PriorityQueue();

-创建初始容量为100的优先级队列

//设置初始容量为100的优先级队列
PriorityQueue q1=new PriorityQueue(100);
  • 使用Collection 中的其他容器创建优先级队列
 //借助集合框架中的其他容器构造
     List<Integer> list =new ArrayList<>();
      list.add(1);
      list.add(2);
    PriorityQueue q2=new PriorityQueue(list);
2. 优先级队列中常用方法

boolean offer(E e):插入元素 e,成功返回true,当e对象为空时,抛出NullPointerException异常;
E peek():获取优先级最高或最低的元素,优先级队列为空时,返回 null
E poll():删除优先级最高或最低的元素,优先级队列为空时,返回 null
int size():获取有效元素的个数;
void clear():清空;
boolean isEmpty():检测优先级队列是否为空;

测试代码如下

public class TestPriorityQueue {
    public static void method(){
        PriorityQueue q=new PriorityQueue();
        q.offer(1);
        q.offer(2);
        q.offer(5);
        q.offer(3);
        q.offer(0);
        q.offer(4);
        System.out.println(q.size());  //6
        System.out.println(q.peek());  //0
        q.poll();
        q.poll();
        System.out.println(q.size()); //4
        System.out.println(q.peek()); //2
        q.clear();
        if(q.isEmpty()){
            System.out.println("优先级队列为空");
        }else{
            System.out.println("优先级队列不为空");
        }
    }
    public static void main(String[] args) {
        method();
   }
}
3. 使用时的注意事项
  • 使用时必须导入PriorityQueue所在的包;
   import java.util.PriorityQueue;
  • 不能插入null对象,否则会抛 空指针异常 (NullPointerException);
      q.offer(null);

就会抛出如下异常优先级队列-堆数据结构_第3张图片

  • PriorityQueue 中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException 异常;
    在这里插入图片描述

  • 没有容量限制,内部支持自动扩容;

当容量小于64时,按照 oldCapacity2 倍方式扩容;
当容量大于等于64,按照 oldCapacity1.5倍方式扩容;
当容量超过 MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE 进行扩容;

  • 插入和删除元素的时间复杂度为 Log2(N);
  • PriorityQueue 底层使用了堆数据结构;
  • PriorityQueue 默认情况下创建的是小堆—即每次获取到的元素都是最小的元素;

系统默认情况下创建的是小堆,如果想要创建大堆,则需要构造比较器--------->实质实现 Comparator 接口,重写该接口中的 compare 方法

代码如下

 public static void method3() {
        PriorityQueue<Integer> q = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1; //o1-o2就是小堆
            }
        });
        q.offer(1);
        q.offer(2);
        q.offer(5);
        q.offer(3);
        System.out.println(q.peek());  //5
}

优先级队列的模拟实现---->堆

优先级队列底层采用了堆的数据结构,下面做介绍~~

堆的概念

简单说:堆就是一棵完全二叉树,且对于任意一个结点均满足:(1)当该结点大于其孩子结点时,就称为大堆;(2)当该结点小于其孩子结点时,就称为小堆;
将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆

优先级队列-堆数据结构_第4张图片
其存储结构就是一段连续的空间,如下所示
在这里插入图片描述
优先级队列-堆数据结构_第5张图片
存储方式如下
在这里插入图片描述

堆的顺序存储

采用顺序存储的原因

  • 堆是一棵完全二叉树,可以实现高效存储,空间利用率也比较高;
  • 完全二叉树的定义下标与数组的第一个下标为0刚好对应;

堆的创建

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
优先级队列-堆数据结构_第6张图片

不难发现:根结点的左右子树均已满足堆的性质(除根结点外,其余结点均是小于其孩子结点的),如下图所示:
优先级队列-堆数据结构_第7张图片
因此,只需要将根结点**向下调整**到合适位置即可创建一个小堆;

向下调整步骤

  • a.让 parent 标记其要调整的结点,child 标记其左孩子的结点;
  • b.当左孩子存在时,即:(child)就循环进行下面两步:(1)当右孩子也存在时,将左右孩子进行比较,让child 标记出找出的较小的那个孩子;(2)将parent与较小的那个孩子进行比较,当 parent>child 标记的孩子时,就进行交换;但交换可能就会导致下面子树不满足堆的特性,因此,需要更新变量(parent=child,child=parent*2+1),继续 b 操作,

注意

在将某结点往下调整时,必须保证该结点的左右子树均满足堆的特性,才可以采用

想不明白就看图

优先级队列-堆数据结构_第8张图片
但明显的子树不满足堆的特性了,因此需要更新变量;

具体代码如下

 public void shiftDown(int[] array){
        int parent=0;
        int child=2*parent+1; //child来标记左孩子
        int size=array.length;

        while(child<size){
          // 右孩子存在时,找左右孩子中较小的孩子,用child进行标记
         if(child+1 < size && array[child+1] < array[child]){
                child += 1;
            }

            // 如果双亲比其最小的孩子还小,说明该结构已经满足堆的特性了
            if (array[parent] <= array[child]) {
                break;
            }else{
                //将双亲与较小的孩子进行交换
                int temp=array[parent];
                array[parent]=array[child];
                array[child]=temp;

                // 当parent中大的元素往下移动时,可能会造成子树不满足堆的性质,因此需要继续向下调整
                parent = child;
                child = parent * 2 + 1;
            }
        }
    }
     public static void main(String[] args) {

        int[] array={27,15,19,18,28,34,65,49,25,37};
        shiftDown(array);
     }
}

时间复杂度分析:
从根比较到叶子,比较的次数为完全二叉树的高度,即 时间复杂度为 O(log2(N))

对于上图1的特殊情况,直接将其根结点调整好即可,但如果序列为{65,37,34,49,28,19,27,18,25,15},又该如何创建呢? 如下图2所示
优先级队列-堆数据结构_第9张图片
思路(任意序列建堆

  • (1)找到当前树中的倒数第一个非叶子结点,该结点也是最后一个结点的双亲所在的位置,而最后一个结点的位置下标为 size-1
    其双亲的下标为:((size-1)-1)/2
  • (2)从该非叶子结点开始往回倒,直到根的位置,遇到一个结点,就以该结点为二叉树进行向下调整;

代码如下

public static void createHeap(int[] array) {
 // 找倒数第一个非叶子节点,从该节点位置开始往前一直到根结点,遇到一个结点,应用向下调整
 int root = ((array.length-2)/2);
 for (; root >= 0; root--) {
 shiftDown(array,root); 
 }
}

使用堆模拟实现优先级队列

  • 堆的插入

(1) 将元素放入到底层空间中;
(2)将最后新插入的结点向上调整,直到满足堆的特性;

public void offer(int e){
            array[size]=e;
            size++;
   //将插入的新元素向上调整
            shiftUp(array,size-1);
        }
  • 堆的删除

(1)将堆顶元素与堆中最后一个元素进行交换;
(2)将堆中有效数据的个数减少一个;
(3) 对堆顶元素进行向下调整;

public Integer poll(){
           int ret=array[0];
           //将堆顶元素与最后一个元素交换
            array[0]=array[size-1];

            //堆中有效元素个数减少一个
             size--;

             //将堆顶元素使用向下调整到合适位置
             shiftDown(array,size,0);
             return ret;
        }
  • 获取堆顶元素
public int peek(){
   return array[0];
}

堆的应用

优先级队列-堆数据结构_第10张图片

class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        // 进行参数检测
     if( arr==null || k <= 0){
        return new int[0];
     }

       
  PriorityQueue<Integer> q = new PriorityQueue<>();
     // 将数组中的元素依次放到堆中
       for(int i = 0; i < arr.length; ++i){
         q.offer(arr[i]);
        }
     // 将优先级队列的前k个元素放到数组中
      int[] ret = new int[k];
      for(int i = 0; i < k; ++i){
        ret[i] = q.poll();
    }
         return ret;

    }
}

你可能感兴趣的:(笔记)