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

一、PriorityQueue 的使用

  • 概念
    队列是一种先进先出(FIFO)的数据结构,但有时候,数据可能带有优先级,出队列时,可能需要优先级高的元素先出队列,在这种情况下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。
  1. 常用接口介绍
    1、JDK:
    提供了两种类型的优先级队列:①PriorityQueue是线程不安全的,②PriorityBlockingQueue是线程安全的。

2、PriorityQueue使用前,应先导入包

import java.util.PriorityQueue;

【注意】

  • 插入的元素不能为null,且元素之间必须要能够进行比较;
  • 插入、删除------在插入和删除元素期间优先级队列中的元素会自定进行调整(不论怎么调整,0号位置的元素始终是最小的);插入和删除的时间复杂度: O(logN)。
  • 底层结构—堆(暂时不用管什么是堆) 也可以通过某种方式使首元素始终最大

3、PriorityQueue包含的接口:

  • 构造方式:

  • PriorityQueue()空的优先级队列:默认的容量为11 ;

PriorityQueue q1 = new PriorityQueue<>();

  • PriorityQueue(capacity):指定初始化容量大小 (capacity不能小于1,如果小于1会抛异常 )

PriorityQueue p2 = new PriorityQueue<>(20);

  • PriorityQueue(集合对象):用一个集合中的元素来构造优先级队列
ArrayList<Integer> list = new ArrayList<>();
     list.add(4);
     list.add(3);
     list.add(2);
     list.add(1);
  //用ArrayList对象来构造一个优先级队列的对象
 //q3中已经包含了三个元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
  • 常用接口:
  • offer(x) :插入元素---->O(logN)
  • poll():删除元素
  • size():获取有效元素个数
  • isEmpty(): 检测是否为空
  • clear():将优先级队列中的元素清空
 public static void TestPriorityQueue() {
        int[] arr = {4, 1, 9, 2, 8, 0, 7, 3, 6, 5};
// 一般在创建优先级队列对象时,如果知道元素个数,建议就直接将底层容量给好
// 否则在插入时需要不多的扩容
// 扩容机制:开辟更大的空间,拷贝元素,这样效率会比较低
        PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
        for (int e : arr) {
            q.offer(e);
        }
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
        // 从优先级队列中删除两个元素之和,再次获取优先级最高的元素
        q.poll();
        q.poll();
        System.out.println(q.size()); // 打印优先级队列中有效元素个数
        System.out.println(q.peek()); // 获取优先级最高的元素
        q.offer(0);
        System.out.println(q.peek()); // 获取优先级最高的元素
// 将优先级队列中的有效元素删除掉,检测其是否为空
        q.clear();
        if (q.isEmpty()) {
            System.out.println("优先级队列已经为空!!!");
        } else {
            System.out.println("优先级队列不为空");
        }
    }

二、堆的概念及实现

  • 概念
    有一个集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
    在这里插入图片描述
    如图所示为小堆,根节点的值小于左右子树的值,反之则为大堆。

  • 存储方式
    堆是一棵完全二叉树,因此可以按照层序的规则采用顺序的方式存储。对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要存储空节点,就会导致空间利用率比较低。

对于具有n个结点的完全二叉树,按照从上到下、从左到右的顺序对节点从0开始编号,假设i为节点在数组中的下标,则有:

  • 若i>0,双亲序号: (i-1)/2; i=0, i为根节点编号,无双亲节点.
  • 2i+1<n, 左孩子序号: 2i+1,否则无左孩子
  • 2i+2<n, 右孩子序号: 2i+2,否则无右孩子

堆的创建

  • 向下调整
    如图, 对于集合{ 27,15,19,18,28,34,65,49,25,37 },根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可。
    在这里插入图片描述
    过程(以小堆为例):
    1、parent为本次调整节点的下标,调整以parent为根的二叉树;
    (调整之前,要保证parent的左右子堆已经满足小堆的性质)

2、检测parent是否满足小堆性质:将parent与其孩子的比较

  • 满足:以parent为根节点的二叉树已经是小堆
  • 不满足:说明parent比其孩子大,将parent与其较小的孩子进行交换,交换后,parent的向下移动,
    可能导致其子树不满足小堆性质,继续调整;
private  void shiftDown(int parent){
        //使用child标记parent较小的孩子
        //默认情况下,先标记左孩子(完全二叉树:可能有左孩子没有右孩子)
        int child = parent * 2 + 1;

        while (child<size){
            //找parent中较小的孩子
            if(res[child+1] < res[child]){//如果右孩子比左孩子小
                child+=1;
            }

            //比较parent与child的大小
            if(res[parent]>res[child]){
                swap(parent,child);

                //调整后,如果子树不满足小堆的性质,继续调整
                parent=child;
                child=parent*2+1;

            }else{
                return;//以parent为根的二叉树已经满足小堆性质
            }
        }
    }
        private void swap(int parent,int child){
           int temp = res[parent];
           res[parent]=res[child];
           res[child]=temp;
        }

  • 堆的创建
    对于根节点的左右子树不满足堆的特性,我们需要不断地进行调整:
public static void createHeap(int[] array) {
 //找到第一个非叶子节点(根) child=parent*2+1 ----- parent = (child-1)/2
        int lastLeaf= (size-1)-1>>1;
        //调整很多个根节点 从下往上找叶子节点
        for (int root = lastLeaf; root >=0 ; root--) {
            shiftDown(root);//每一个根节点都要向下调整
        }
}
  • 堆的插入
    堆的插入总共需要两个步骤:
  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质
 private boolean offer(int x){
            grow();//扩容

            //1、将元素尾插到数组中
            res[size++] = x;

            //2、检测新元素插入后是否破坏小堆的性质
            shiftUp(size-1);//向上调整
            return true;
        }
        
  private void shiftUp(int child) {
            int parent = (child - 1) >> 1;

            if (res[parent] > res[child]) {
                swap(parent, child);


                //调整后,可能导致不满足堆的性质 继续向上调整
                child = parent;
                parent = (child - 1) >> 1;
            } else {
                return;
            }
        }
        
  private void swap(int parent,int child){
           int temp = res[parent];
           res[parent]=res[child];
           res[child]=temp;
        }
   private void grow(){
          if(size>= res.length){
              int oldCapacity = res.length;
              int newCapacity = oldCapacity+ ((oldCapacity<64)?(oldCapacity + 2):(oldCapacity >> 1));
            }
        }
  • 堆的删除
    堆的删除一定删除的是堆顶元素:
  1. 将堆顶元素对堆中最后一个元素交换;
  2. 将堆中有效数据个数减少一个;
  3. 对堆顶元素进行向下调整;
private int poll(){
          int a = res[0];
          swap(0,size-1);//0号元素和新插入元素交换
          size--;

          shiftDown(0);//向下调整
          return a;
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值