使用 Java 实现优先队列(小根堆)

本文介绍了优先队列的基本模型,并探讨了三种实现方式:链表、二叉查找树和堆。重点讲解了基于堆的实现,包括二叉堆的结构和堆序性。文中详细阐述了如何插入、删除元素以及建堆的过程,最后提供了完整的Java代码实现。
摘要由CSDN通过智能技术生成

优先队列基本模型

优先队列基本模型
优先队列的基本模型十分简单。可以向队列插入一个元素,也可以从队列删除一个元素。但需要注意的是,基于堆结构的优先队列,插入和删除操作极可能破坏优先队列的结构性和有序性。因此每完成一次操作,都需要重新调整队列结构以保证总是满足这两个特性。

优先队列的实现

优先队列的实现方式有很多,但最常用的还是 堆(heap) 结构。下面我们先简单讨论几种其它方式的实现,再动手实现一个基于堆结构的优先队列。

基于链表实现

优先队列的核心思想便是每次取出的元素都是当前队列优先级(优先级的逻辑意义可以自己定义)最高的,故你可以使用一个单链表,每次在表头插入一个元素,删除元素时遍历整个链表从而删除最小元素。这样的话插入操作为 O(1),删除操作为 O(N)。也可以通过排序算法让单链表始终有序,这样的话插入操作为 O(N),删除操作为 O(1)。

基于二叉查找树实现

二叉查找树结构很容易保证每次取出的都是优先级最高的元素。你只需要不断地访问左子树(假设左儿子优先级大于根节点),最终访问的节点一定是优先级最高的。查找树的插入和删除操作都能将时间复杂度保证在 O(logN),甚至你可以使用 AVL 树来避免过多删除操作使查找树严重失衡。但使用查找树来仅仅实现优先队列有些大材小用了。

基于堆实现
二叉堆(binary heap)

堆和二叉查找树一样,具有两个性质:结构性堆序性。对堆的操作很可能破坏这两个性质。因此每次操作都需要重新调整堆的结构,而堆的一个巨大优势便是结构调整的效率很高,你只需要调整堆中的一条链路即可,平均时间复杂度仅为 O(logN)
结构性:
堆的结构是一颗完全二叉树,一颗高为 h 的完全二叉树有 2h2h+1 -1 个节点。意味着具有 N 个节点的完全二叉树高为 ⌊logN⌋。由于完全二叉树结构的规律性,堆常用一个数组即可实现。使用数组实现的堆,其 i 节点的父节点为 (i + 1) / 2,左儿子为 2i,右儿子为 2i + 1
堆序性:
以小根堆为例,任意节点小于它的所有后裔。因此根节点就是堆中的最小 元素。

开始实现一个支持泛型的优先队列
属性
    // 优先队列默认容量
    private static final int DEFAULT_CAPACITY = 10;
    // 当前元素个数
    private int currentSize;
    // 存储元素的数组
    private E[] array;
插入一个元素

为了保证堆的结构性,我们应该在数组中最后一个元素的下一位置添加一个空节点 n 暂时作为新元素的插入位置,这样可以使得堆依然是一个完全二叉树,但问题是堆序性可能已经被打破。接着让待插入元素和节点 n 的父节点比较,如果待插入元素优先级低于 n 的父节点,则直接将元素插入在节点 n 处。否则,交换节点 n 和其父节点。接着,重复这个比较过程,使节点 n 不断向上调整,直至找到合适的位置并将新元素插入。这个过程称为 上滤(percolate up),由于节点 n 无论怎么调整,只会涉及完全二叉树中的某一条链路(因为只会和其父节点交换),故上滤操作的时间复杂度为 O(logN)

    public void insert(E x) {
   
        if (currentSize == array.length - 1) {
   
            enlargeArray(array.length * 2 + 1);
        }
        // Percolate up
        int hole = ++currentSize;
        array[hole] = x;
        percolateUp(hole);
    }
	// 上滤操作
    private void percolateUp(int hole) {
   
        E temp = array[hole];
        for (;temp.compareTo(array[hole / 2]) < 0; hole /= 2) {
   
            array[hole] = array[hole / 2];
        }
        array[hole] = temp;
    }
删除优先级最高的元素

删除优先级最高的元素一般可以分为两步:找出优先级最高的元素和删除它。在基于堆结构的优先队列中,优先级最高的元素一定是根,因此找到优先级最高的元素的代价仅为 O(1)。随后,我们不直接删除根节点,因为这会破坏堆的结构性。我们先暂时让根为空,然后将堆中最底层的最后一个元素 n 移动到根处,这样删除的是最后一个元素,堆依然保证了结构性。但由于临时的根 n 并不是优先级最高的元素,故堆序性遭到了破坏。我们让 n 和它两个儿子中优先级较高的一个交换位置,使得 n 下降一层,这样堆中优先级最高的元素成为了新的根。接着让 n 重复和它优先较高的儿子进行比较,如果 n 的优先级比其低,则继续交换位置,否则 n 达到了正确位置,堆结构调整结束。节点 n 不断下降的过程也被称为 下滤(percolate down),和上滤一样,下滤也只会涉及堆中的一条链路,故时间复杂度为 O(logN)

    public E deleteMin() {
   
        if (isEmpty())
        return null;
        E minItem = findMin();
        array[
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值