目录

一.什么是优先级队列

二.堆——优先级队列的底层

2.1堆的定义与性质

2.2堆的存储方式

2.3堆的创建——向下调整

2.4堆的插入和删除

三.Java中的PriorityQueue

3.1使用注意:

3.2构造方法

3.3常用方法

3.4扩容机制

四.堆排序

总结:


一.什么是优先级队列

        优先级队列是一种特殊的队列,它支持两种核心操作:

        1.返回最高优先级对象

        2.插入新的对象

与普通队列不同,优先级队列不是先进先出,而是高优先级的先出。如果优先级相同,则通常按插入顺序或元素自然顺序决定

二.堆——优先级队列的底层

        在JDK1.8中,PriorityQueue的底层使用的是堆这种数据结构。堆本质上是一颗完全二叉树,并且其存储方式为顺序存储(即使用数组)。

2.1堆的定义与性质

        对于集合K={……},将其按完全二叉树的顺序存储在一维数组中。

        小根堆:任意节点Ki<=K2i+1且Ki<=K2i+2

        大根堆:任意节点KI>=K2i+1且Ki>=K2i+2

堆的两个重要性质:

        堆中的某个节点的值总是不大于(或不小于)其父节点的值。

        堆总是一个完全二叉树。

2.2堆的存储方式

        由于的是完全二叉树,使用数组存储可以节省空间。对于下标为 i 的节点:

若  i==0,则为根节点

父节点:(i-1)/2

左孩子:2*i+1;右孩子:2*i+2

2.3堆的创建——向下调整

        给定一个集合{27,15,19,18,28,34,65,49,25,37},我们如何将它建成一个小根堆?如果根节点的左右子树已经满足堆性质,只需要将根节点向下调整到合适位置即可

向下调整法(以小根堆为例):

1.用parent标记带调整节点,child标记其左孩子

2.如果child存在(child<size),则:

        找出左右孩子中较小的一个,用chilid标记

        若array[parent]<=array[child],则调整结束。

        否则,交换parent和child的值,然后继续向下:parent=child,child=2*parent+1

代码实现:

public void shiftDown(int[] arr,int parent){
    int child=2*parent+1;
    int size =arr.length;
    while(child<size){
        if(child+1<size&&arr[child]>arr[child+1]){
            child++;
        }
        if(arr[parent]<=arr[child]){
            break;
        }

        //交换        
        int temp=arr[parent];
        arr[parent]=arr[child];
        arr[child]=temp;

//继续向下
        parent=child;
        child=2*parent+1;
    }
}

建堆:如果整个树都不满足堆性质,需要从最后一个非叶子节点开始,向前依次向下调整

public static void createHeap(int[] arr){
    int root=(arr.length-2)/2//最后一个非叶子节点下标
    for(;root>=0;root--){
        shiftDown(arr,root);
    }
}

时间复杂度:建堆的时间复杂度为O(N)

2.4堆的插入和删除

插入:

1.将元素放入数组末尾

2.从该节点开始向上调整,直到满足堆性质。

public void shiftUp(int child){
    int parent=(child-1)/2;
    while(child>0){
        if(arr[parent]<=arr[child]){
            break;
        }
        int temp=arr[parent];
        arr[parent]=arr[child];
        arr[child]=temp;

        child=parent;
        parent=(child-1)/2;    
    }
}

删除(删除堆顶)

1.将堆顶元素与最后一个元素交换

2.有效元素个数减一

3.对新的堆顶执行向下调整

public class MyPriorityQueue {
    private int[] array = new int[100]; // 简化,不考虑扩容
    private int size = 0;

    public void offer(int e) {
        array[size++] = e;
        shiftUp(size - 1);
    }

    public int poll() {
        int oldValue = array[0];
        array[0] = array[--size];
        shiftDown(0);
        return oldValue;
    }

    public int peek() {
        return array[0];
    }

    // shiftDown 和 shiftUp 方法同上,此处省略
}

注意:删除只能删除堆顶元素,这是优先级队列的特性。

三.Java中的PriorityQueue

        java集合框架提供了PriorityQueue(线程不安全)和PriorityBlockingQueue(线程安全)

3.1使用注意:

        1.必须导入包:import java.util.PriorityQueue;

        2.元素必须可以比较哦啊,要么实现Comparable接口,要么构造时提供Comparator,否则抛出ClassCastException

        3.不能插入null

        4.无容量限制(内部自动扩容)

        5.默认是小堆:每次取出都是最小元素,可以通过Comparator该为大堆

        6.插入/删除时间复杂度O(logN)

3.2构造方法

构造器 说明
PriorityQueue() 默认容量为11
PriorityQueue(int initialCapacity) 指定初始容量
PriorityQueue(Collection<? extends E> c) 用已有集合构造

示例:

PriorityQueue<Integer> pq = new PriorityQueue<>();
// 用集合构造
ArrayList<Integer> list = new ArrayList<>();
list.add(4); list.add(3); list.add(2); list.add(1);
PriorityQueue<Integer> pq2 = new PriorityQueue<>(list);
System.out.println(pq2.peek()); // 输出 1

创建大堆:

class IntCmp implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o2 - o1; // 降序,大根堆
    }
}
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new IntCmp());

3.3常用方法

方法 说明
boolean offer(E e) 插入元素,失败抛异常
E peek() 获取堆顶元素(不删除)
E poll() 移除并返回堆顶元素
int size() 元素个数
void clear() 清空
boolean isEmpty() 判空

3.4扩容机制

        容量小于64,新容量=oldCapacity*2+2

        容量大于64,新容量=oldCapacity+oldCapacity >>1

源码片段:

private void grow(int minCapacity) {
    int oldCapacity = queue.length;
    int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                                     (oldCapacity + 2) :
                                     (oldCapacity >> 1));
    // overflow handling...
}

四.堆排序

        1.建堆:升序——>建大堆;降序——>建小堆

        2.排序:将堆顶元素与末尾元素交换,堆大小减一,再向下调整

public static void heapSort(int[] arr) {
    // 1. 建大堆
    for (int i = (arr.length - 2) / 2; i >= 0; i--) {
        shiftDownBig(arr, i, arr.length);
    }
    // 2. 交换 + 调整
    int end = arr.length - 1;
    while (end > 0) {
        int temp = arr[0];
        arr[0] = arr[end];
        arr[end] = temp;
        shiftDownBig(arr, 0, end);
        end--;
    }
}
// shiftDownBig 实现类似,注意比较符号相反

时间复杂度:O(n Log n),空间:O(1),不稳定

总结:

        优先级队列:一种按照优先级出队的抽象数据类型,底层通常使用堆实现

        堆:一个完全二叉树,顺序存储,分为大根堆,小根堆

        建堆:时间复杂度O(N),利用向下调整;插入删除O(logN).

        Java中PriorityQueue默认小堆,可自定义比较器变为大堆,注意元素必须可比较且不能为null

        堆排序:不稳定排序,时间复杂度O(N logN)

更多推荐