数据结构--PriorityQueue

目录

[1. 优先级队列](#1. 优先级队列)

[1.1 概念](#1.1 概念)

[2. 优先级队列的模拟实现](#2. 优先级队列的模拟实现)

[2.1 堆的概念](#2.1 堆的概念)

[2.2 堆的性质](#2.2 堆的性质)

[2.3 堆的两种结构](#2.3 堆的两种结构)

[2.4 堆的存储方式](#2.4 堆的存储方式)

[2.5 堆的创建](#2.5 堆的创建)

[2.5.1 堆向下调整](#2.5.1 堆向下调整)

代码

[2.5.2 堆的创建](#2.5.2 堆的创建)

[2.6 堆的插入与删除](#2.6 堆的插入与删除)

[2.6.1 堆的插入](#2.6.1 堆的插入)

代码

[2.6.2 堆的删除](#2.6.2 堆的删除)

代码

3.常用接口介绍

[3.1 PriorityQueue的特性](#3.1 PriorityQueue的特性)

[3.2 PriorityQueue常用接口介绍](#3.2 PriorityQueue常用接口介绍)

[1. 优先级队列的构造](#1. 优先级队列的构造)

[2. 插入/删除/获取优先级最高的元素](#2. 插入/删除/获取优先级最高的元素)

[4. 堆的应用](#4. 堆的应用)

[4.1 PriorityQueue的实现](#4.1 PriorityQueue的实现)

[4.2 堆排序](#4.2 堆排序)

[1. 建堆](#1. 建堆)

[2. 利用堆删除思想来进行排序](#2. 利用堆删除思想来进行排序)


**1.**优先级队列

1.1****概念

队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级,一般出队
列时,可能需要优先级高的元素先出队列 ,使用队列显然不合适,比如:排座位时可能会让成绩好的同学先挑座位。 在这种情况下, 数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue)

**2.**优先级队列的模拟实现

2.1****堆的概念

如果有一个 关键码的集合 K = {k0 k1 k2 ... kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储在一 个一维数组中 ,并满足: Ki <= K2i+1 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0 , 1 , 2... ,则 称为小堆 ( 或大堆) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

2.2 堆的性质

1.堆中某个节点的值总是不大于或不小于其父节点的值(在最大堆中,任意一个节点的值都不会大于它的父节点的值,在最小堆中,任意一个节点的值都不会小于它的父节点的值 )。
2.堆总是一棵完全二叉树。

2.3 堆的两种结构

2.4 堆的存储方式

从堆的概念可知, 堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储

注意:对于 非完全二叉树,则不适合使用顺序方式进行存储 ,因为为了能够还原二叉树, 空间中必须要存储空节 点,就会导致空间利用率比较低
将元素存储到数组中后,可以根据二叉树章节的性质 5 对树进行还原。假设 i 为节点在数组中的下标,则有:
·如果 i 为 0 ,则 i 表示的节点为根节点,否则 i 节点的双亲节点为 (i - 1)/2
·如果 2 * i + 1 小于节点个数,则节点 i 的左孩子下标为 2 * i + 1 ,否则没有左孩子
·如果 2 * i + 2 小于节点个数,则节点 i 的右孩子下标为 2 * i + 2 ,否则没有右孩子

2.5****堆的创建

2.5.1****堆向下调整

对于集合 { 27,15,19,18,28,34,65,49,25,37 } 中的数据,如果将其创建成堆呢?

仔细观察上图后发现: 根节点的左右子树已经完全满足堆的性质,因此只需将根节点向下调整好即可
向下过程 ( 以小堆为例 )

  1. 让 parent 标记需要调整的节点, child 标记 parent 的左孩子 ( 注意: parent 如果有孩子一定先是有左孩子 )

  2. 如果 parent 的左孩子存在,即 :child < size ,进行以下操作,直到 parent 的左孩子不存在
    ·parent 右孩子是否存在,存在找到左右孩子中最小的孩子,让 child 进行标
    ·将 parent 与较小的孩子 child 比较,如果:

    public void shiftDown(int parent, int usedSize) {
    int child = (2 * parent) + 1;
    while (child < usedSize) {
    //先定义再进行这样的判断,可以有效避免数组越界的问题
    int rightChild = child + 1;
    // 确保右孩子节点存在且不越界
    if (rightChild < usedSize && elem[rightChild] > elem[child]) {
    child++;
    }
    if (elem[child] > elem[parent]) {
    swap(child, parent);
    parent = child;
    child = (2 * parent) + 1;
    } else {
    break;
    }
    }
    }

     public void swap(int i,int j){
         int tmp = elem[i];
         elem[i] = elem[j];
         elem[j] = tmp;
     }
    

代码

public void shiftDown(int parent, int usedSize) {
        int child = (2 * parent) + 1;
        while (child < usedSize) {
            //先定义再进行这样的判断,可以有效避免数组越界的问题
            int rightChild = child + 1;
            // 确保右孩子节点存在且不越界
            if (rightChild < usedSize && elem[rightChild] > elem[child]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = (2 * parent) + 1;
            } else {
                break;
            }
        }
    }

    public void swap(int i,int j){
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

2.5.2 堆的创建

public void createHeap(){
        for (int parent = (UsedSize-1-1) / 2; parent >=0 ; parent--) {
            //自己定义一个停止标志
            shiftDown(parent,UsedSize);
        }
    }

2.6****堆的插入与删除

2.6.1****堆的插入

  1. 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
  2. 将最后新插入的节点向上调整,直到满足堆的性质

代码

public void offer(int val){

        //判断当前数组是满的吗?
        //1.不满就放在最后一个位置
        //2.满就扩容

        if(isFull()){
            this.elem = Arrays.copyOf(elem,2*elem.length);
        }

        //扩容完毕之后将插入的数据放进去
        this.elem[UsedSize] = val;

        //直接从插入的位置开始判断大根堆
        shiftUp(UsedSize);

        //加入新的元素要将计数器更新
        UsedSize++;
    }

public boolean isFull(){
        return UsedSize == elem.length;
    }

2.6.2 堆的删除

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

代码

public int poll(){
        int tmp = elem[0];
        swap(0,UsedSize-1);
        UsedSize--;
        shiftDown(0,UsedSize);
        return tmp;
    }

public void shiftDown(int parent, int usedSize) {
        int child = (2 * parent) + 1;
        while (child < usedSize) {
            //先定义再进行这样的判断,可以有效避免数组越界的问题
            int rightChild = child + 1;
            // 确保右孩子节点存在且不越界
            if (rightChild < usedSize && elem[rightChild] > elem[child]) {
                child++;
            }
            if (elem[child] > elem[parent]) {
                swap(child, parent);
                parent = child;
                child = (2 * parent) + 1;
            } else {
                break;
            }
        }
    }

**3.**常用接口介绍

3.1 PriorityQueue****的特性

Java 集合框架中提供了 PriorityQueuePriorityBlockingQueue 两种类型的优先级队列, PriorityQueue 是线
程不安全的, PriorityBlockingQueue 是线程安全的 ,本文主要介绍 PriorityQueue 。

注意:

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

    importjava.util.PriorityQueue;

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

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

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

  5. PriorityQueue 底层使用了堆数据结构

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

3.2 PriorityQueue****常用接口介绍

1. 优先级队列的构造

static void TestPriorityQueue(){
// 创建一个空的优先级队列,底层默认容量是11
PriorityQueue<Integer> q1 = new PriorityQueue<>();
// 创建一个空的优先级队列,底层的容量为initialCapacity
PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
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);
System.out.println(q3.size());
System.out.println(q3.peek());
}

注意:默认情况下,PriorityQueue队列是小堆,如果需要大堆需要用户提供比较器

// 用户自己定义的比较器:直接实现Comparator接口,然后重写该接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
p.offer(4);
p.offer(3);
p.offer(2);
p.offer(1);
p.offer(5);
System.out.println(p.peek());
}
}

此时创建出来的就是一个大堆

2. 插入**/删除/**获取优先级最高的元素


static void TestPriorityQueue2(){
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("优先级队列不为空");
}
}

int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity

优先级队列的扩容说明:
`如果容量小于 64 时,是按照 oldCapacity 的 2 倍方式扩容的
`如果容量大于等于 64 ,是按照 oldCapacity 的 1.5 倍方式扩容的
`如果容量超过 MAX_ARRAY_SIZE ,按照 MAX_ARRAY_SIZE 来进行扩容

**4.**堆的应用

4.1 PriorityQueue****的实现

用堆作为底层结构 封装优先级队列

4.2****堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1. 建堆

升序:建大堆 shiftup()
降序:建小堆 shiftdown()

2. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。


希望可以帮到读者

相关推荐
欧阳枫落5 分钟前
python 2小时学会八股文-数据结构
开发语言·数据结构·python
手握风云-18 分钟前
零基础Java第十六期:抽象类接口(二)
数据结构·算法
<但凡.1 小时前
编程之路,从0开始:知识补充篇
c语言·数据结构·算法
f狐0狸x2 小时前
【数据结构副本篇】顺序表 链表OJ
c语言·数据结构·算法·链表
Tmbcan2 小时前
zkw 线段树-原理及其扩展
数据结构·zkw 线段树
乐悠小码2 小时前
数据结构------队列(Java语言描述)
java·开发语言·数据结构·链表·队列
爱吃生蚝的于勒7 小时前
C语言内存函数
c语言·开发语言·数据结构·c++·学习·算法
workflower13 小时前
数据结构练习题和答案
数据结构·算法·链表·线性回归
一个不喜欢and不会代码的码农13 小时前
力扣105:从先序和中序序列构造二叉树
数据结构·算法·leetcode