优先级队列(堆)

所谓优先级队列,就是在出队列的时候,不在遵循先进先出的规则,而是先出优先级最高的数据。堆这种数据结构实际上是一颗完全二叉树进行调整得到的。

堆的概念

把一组数据集合K中的所有元素k0、k1、k2、k3……按照完全二叉树的顺序存储到一维数组当中,并且满足ki <= k2i+1且ki <= k2i+2(或者ki >= k2i+1且ki >= k2i+2),则称为小堆或小根堆(大堆或大根堆)。也就是说堆中任何一个结点总是不大于(或不小于)它的父节点。

逻辑结构

存储结构

堆属于完全二叉树,存储方式为顺序存储。

堆的模拟实现

建堆

堆的模拟实现首先需要建堆,建堆可以通过向下调整的方式实现,以建大根堆为例:

首先需要父结点的值同时大于左孩子和右孩子,对于左右孩子,又分别是一个大根堆,那么向下调整的思路为:将父结点同左右孩子结点值较大的相比较,如果父结点的值小于值较大的孩子结点,将二者进行交换,交换完的结点也需要向下调整。

向下调整

public void createHeap(int[] array) {
    for (int parent = (usedSize - 2) / 2; parent >= 0; parent--) {
        shiftDown(parent, usedSize);
    }
}

/**
 *
 * @param root 是每棵子树的根节点的下标
 * @param len  是每棵子树调整结束的结束条件
 */
private void shiftDown(int root,int len) {
    int maxChild = root * 2 + 1;
    while (maxChild < len) {
        if (maxChild + 1 < len && elem[maxChild] < elem[maxChild + 1]) {
            maxChild++;
        }
        if (elem[maxChild] > elem[root]) {
            swap(elem, maxChild, root);
            root = maxChild;
            maxChild = root * 2 + 1;
        }else {
            break;
        }
    }
}

向下调整建堆的时间复杂度:O(N)

由于每个结点都需要进行向下调整,而每个结点向下调整的时间复杂的为树的高度,譬如上图:第一层有一个结点,它向下调整的时间复杂度为树的高度,即第一层为:1 * (4 - 1) (树的高度为4),第二层有两个结点:2 * (4 - 2),第三层:4 * (4 - 3),第四层最多会有8个结点,因此时间复杂度为:8 * (4 - 4)

那么对于一颗高度为h的满二叉树来说,其时间复杂度为:

T(n) = 2^0 * (h-1) + 2^1 * (h-2) + 2^2 * (h-3) + …… + 2^(h-2) * 1 + 2^(h-1) * 0

利用错位相减法可得T(n) = 2^h - 1 - h

一颗高度为h的满二叉树与结点个数n的关系为:n = 2^h - 1; -> h = log2(n + 1)

即T(n) = n - log2(n + 1) - 1≈n

入队和出队

入队和出队,不会改变堆的性质,一个大根堆(小根堆),在入队或出队后,仍然属于大根堆(小根堆)。

入队(插入):把入队元素放入队尾,由于其已经是大根堆,只需调整新入队元素和其父结点,若新入队元素大于父结点值,交换二者的值,父结点由于值改变,需要依次向上比较,倘若小于父结点,则说明已经满足大根堆,调整结束:

public void offer(int val) {
    //堆为数组,需要判断数组是否放满元素
    if (isFull()) {
        //扩容
        elem = Arrays.copyOf(elem, 2 * elem.length);
    }
    elem[usedSize++] = val;
    shiftUp(usedSize - 1);
}

private void shiftUp(int child) {
    int parent = (child - 1) / 2;
    while (child > 0) {
        if (elem[child] > elem[parent]) {
            swap(elem, child, parent);
            child = parent;
            parent = (child - 1) / 2;
        }else {
            break;
        }
    }
}

出队(删除):每次删除只能删除队头元素,由于没有办法直接删除队头元素,做法是将队头元素与队尾元素交换,自然地舍弃队尾元素,再将新的队头元素向下调整即可:

public void pollHeap() {
    if (isEmpty()) {
        return;
    }
    swap(elem, 0, elem.length - 1);
    usedSize--;//访问不到即删除
    shiftDown(0, usedSize);
}

获取堆顶元素比较简单,直接返回队头元素即可:

public int peekHeap() {
    return usedSize == 0 ? null : elem[0];
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值