Java数据结构 堆-优先级队列

优先级队列

概念

队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列

数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

优先级队列的实现

堆的概念

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

对于大堆来说,当前节点一定是小于等于父节点 => 根元素就是最大值
对于小堆来说,当前节点一定是大于等于父节点 => 根元素就是最小值

堆的性质:

  • 堆中某个节点的值总是不大于或不小于其父节点的值
  • 堆总是一棵完全二叉树

堆的存储方式

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

注意:
对于非完全二叉树,则不适合使用顺序方式进行存储,因为为了能够还原二叉树,空间中必须要
存储空节点,就会导致空间利用率比较低

假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数,则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数,则节点i的右孩子下标为2 * i + 2,否则没有右孩子

堆的创建

堆向下调整

向下过程(以大堆为例):

  • 让parent标记需要调整的节点,child 标记 parent 的左孩子(注意:parent 如果有孩子一定先是有左
    孩子)
  • 如果parent的左孩子存在,即:child < size, 进行以下操作,直到parent的左孩子不存在
    • parent右孩子是否存在,存在找到左右孩子中最大的孩子,让 child 进行标
    • 将 parent 与较大的孩子 child 比较,如果:
      • parent 小于较大的孩子 child,调整结束
      • 否则:交换 parent 与较大的孩子 child,交换完成之后,parent 和 child 中的小元素向下移动,可能导致子树不满足对的性质,因此需要继续向下调整,即 parent = child;child = parent*2+1; 然后继续2。
    public static void shiftDown(int[] arr, int size, int index) {
        int parent = index;
        int child = 2 * parent + 1;
        while (child < size) {
            if (child + 1 < size && arr[child + 1] > arr[child]) {
                child = child + 1;
            }
            if (arr[child] > arr[parent]) {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
            }
            parent = child;
            child = 2 * parent + 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;
        }
    }

堆的基本操作

插入

堆的插入总共需要两个步骤:

  1. 先将元素放入到底层空间中(注意:空间不够时需要扩容)
  2. 将最后新插入的节点向上调整,直到满足堆的性质
    public void offer(int val) {
        if (size > arr.length) {
            return;
        }
        arr[size] = val;
        size++;
        shiftUp(arr, size, size - 1);
    }

删除(只能删除堆顶元素)

  1. 将堆顶元素对堆中最后一个元素交换
  2. 将堆中有效数据个数减少一个
  3. 对堆顶元素进行向下调整
    public Integer poll() {
        if (size == 0) {
            return null;
        }
        int result = arr[0];
        arr[0] = arr[size - 1];
        size--;
        shiftDown(arr, size, 0);
        return result;
    }

获取堆顶元素

    public Integer peek() {
        if (size == 0) {
            return null;
        }
        return arr[0];
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值