优先级队列(堆)

1.优先级队列

1.1概念

前面介绍过队列, 队列是一种先进先出 (FIFO) 的数据结构 ,但有些情况下, 操作的数据可能带有优先级 ,一般出队列时,可能需要优先级高的元素先出队列 ,该中场景下,使用队列显然不合适。 在这种情况下,我们的数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加 新的对象 。这种数据结构就是 优先级队列 (Priority Queue)

1.3常用接口

1.2.1 PriorityQueue的特性

Java 集合框架中提供了 PriorityQueue PriorityBlockingQueue 两种类型的优先级队列, PriorityQueue 是线程不安全的, PriorityBlockingQueue 是线程安全的
 
 
关于 PriorityQueue 的使用要注意:
1. 使用时必须导入 PriorityQueue 所在的包,即:
import java.util.PriorityQueue;
2. PriorityQueue 中放置的 元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出 ClassCastException 异常
3. 不能 插入 null 对象,否则会抛出 NullPointerException
4. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
5. 插入和删除元素的时间复杂度为 O(log_2N)
6. PriorityQueue 底层使用了堆数据结构
 
 
 
1.2.2 PriorityQueue 常用接口介绍
 
 
1. 优先级队列的构造 (常见)。
import java . util . PriorityQueue ;

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

 

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… ,则 称为 小堆 ( 或大堆 ) 。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
 
堆的性质:
a. 堆中某个节点的值总是不大于或不小于其父节点的值;
b. 堆总是一棵完全二叉树。

 

 

2.2 堆的存储方式

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

 

2.3堆的创建

2.3.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 比较,如果:parent小于较小的孩子 child ,调整结束。
 
否则:交换 parent 与较小的孩子 child ,交换完成之后, parent 中大的元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即parent = child child =parent*2+1; 然后继续 2
 
 
public void shiftDown(int[] array, int parent) {
    // child先标记parent的左孩子,因为parent可能右左没有右
    int child = 2 * parent + 1;
    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 t = array[parent];
       array[parent] = array[child];
       array[child] = t;
            
            // parent中大的元素往下移动,可能会造成子树不满足堆的性质,因此需要继续向下调整
            parent = child;
            child = parent * 2 + 1;
       }
   }
}

 

注意:在调整以parent为根的二叉树时,必须要满足parent的左子树和右子树已经是堆了才可以向下 调整。
 
堆向上调整(小堆为例)
    public static void shiftUp(int[] arr, int size, int index) {
        int child = index;
        int parent = (child - 1) / 2;
        while (child > 0) {
            if (arr[parent] > arr[child]) {
                // 交换两个元素
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
            } else {
                break;
            }
            child = parent;
            parent = (child - 1) / 2;
        }
    }

2.3.2堆的创建

对于普通序列,根节点的左右子树不满足堆的特性:

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

2.4 堆的插入与删除

堆的插入:

1. 先将元素放入到底层空间中 ( 注意:空间不够时需要扩容 )
2. 将最后新插入的节点向上调整,直到满足堆的性质
 
堆的删除:
 
1. 将堆顶元素对堆中最后一个元素交换
2. 将堆中有效数据个数减少一个
3. 对堆顶元素进行向下调整
public static void createHeap(int[] array) {

        for (int i = (array.length - 1 - 1) / 2; i >= 0; i--) {
            shiftDown(array, array.length, i);
        }
    }

    // 这就表示当前存储堆的数组
    private int[] arr = new int[100];
    private int size = 0;

    // 往堆中插入元素
    public void offer(int val) {
        if (size >= arr.length) {
            // 插入失败, 已经满了.
            // 也可以实现扩容逻辑.
            return;
        }
        // 先是把这个元素给尾插到数组末尾
        arr[size] = val;
        size++;

        // 把最后的这个元素进行向上调整
        shiftUp(arr, size, size - 1);
    }

    // 类似的, 也可以使用向上调整(基于 offer) 的方式来建堆
    public void createHeap2(int[] arr) {
        // 循环遍历数组, 把元素通过 offer 方法插入即可
        for (int x : arr) {
            offer(x);
        }
    }

    // 获取堆顶元素
    public Integer peek() {
        if (size == 0) {
            return null;
        }
        return arr[0];
    }

    // 删除操作(一定是删除堆顶的元素)
    public Integer poll() {
        if (size == 0) {
            return null;
        }
        int result = arr[0];

        // 交换 0 号元素和 size - 1 号元素
        int tmp = arr[0];
        arr[0] = arr[size - 1];
        arr[size - 1] = tmp;

        // size--, 把最后的元素干掉
        size--;

        // 从 0 号元素开始, 往下进行向下调整
        shiftDown(arr, size, 0);
        return result;
    }

 

 
 
 
 
 
 
 
 
 
 
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值