优先队列的Java二叉堆实现

优先队列

接口

接口见 队列的Java实现

基于***二叉堆*** 的实现

原理

实现一个完全二叉树,并且保证父级节点大于等于它的子节点(最大的元素优先出队)或者保证父级节点小于等于它的子节点(最小元素优先出队)

构造一个这样的二叉堆需要如下步骤:

  • 在入队时:
    1. 将入队元素放入堆的末尾
    2. 将新入队的元素与它的父级节点进行比较,如果满足优先条件,则将他们的交换位置
    3. 以此进行步骤2的操作直至新入队元素不满足优先条件或者到达堆顶
  • 在出队时:
    1. 将堆顶的元素与堆尾的元素互换位置
    2. 删除堆尾元素
    3. 将新的堆顶元素与它的子节点进行比较,如果不满足优先条件,则将它与子节点满足优先条件的元素交换位置
    4. 以此进行步骤3的操作直至新的堆顶元素满足优先条件或者到达堆的边界

命题

  • 对于一个含有 N N N 个元素的基于的优先队列,插入元素 操作只需要不超过 l o g 2 N + 1 log_2N+1 log2N+1 次比较,删除堆顶元素 的操作需要不超过 2 l o g 2 N 2log_2N 2log2N 次比较

最大的元素优先出队

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * 基于二叉堆的优先队列实现
 * 最大的元素优先出队
 */
public class BiMaxPq<Key extends Comparable<Key>> implements IQueue<Key> {
    // 当一颗二叉树的每个节点都大于等于它的子节点时,它被称为堆有序
    // 根节点是堆有序的二叉树的最大节点
    // 二叉堆是一组能够用堆有序的完全二叉树排序的元素,并在数组中按照层级存储(不使用数组的第一个位置)

    /**
     * 基于堆的完全二叉树
     */
    private Key[] pq;

    /**
     * 存储于 pq[1..high] 中,pq[0] 没有使用
     */
    private int high = 0;

    /**
     * 创建一个优先队列
     */
    BiMaxPq() {
        this(1);
    }

    /**
     * 创建一个最大容量为max的有限队列
     *
     * @param max
     *            最大容量
     */
    BiMaxPq(int max) {
        pq = (Key[])new Comparable[max + 1];
    }

    /**
     * 使用 a[] 中的元素创建一个有限队列
     *
     * @param a
     */
    BiMaxPq(Key[] a) {
        
	    high = a.length;

        pq = (Key[])new Comparable[high + 1];

        for (int i = 0; i < high; i++) {
            pq[i + 1] = a[i];
        }

        for (int k = high / 2; k >= 1; k--) {
            sink(k);
        }
    }

    /**
     * 向优先队列插入一个元素
     *
     * @param v
     */
    @Override
    public void enqueue(Key v) {

        if (isFull()) {
            // 如果数组满了,将数组扩容一倍
            resize(pq.length * 2);
        }

        pq[++high] = v;

        swim(high);
    }

    /**
     * 将最大的元素 上浮
     * 
     * 原理: 比对 当前元素 和 它的父级元素的大小;如果大于等于父级元素则与父级元素交换位置;对于 完全二叉树 k 节点的 父节点为 k/2
     * 
     * @param k
     *            指针位置
     */
    private void swim(int k) {
        while (k > 1 && less(k / 2, k)) {
            exchange(k / 2, k);
            k = k / 2;
        }
    }

    /**
     * 返回最大元素
     *
     * @return 最大元素
     */
    public Key max() {
        return pq[1];
    }

    /**
     * 删除并返回最大元素
     *
     * @return 最大元素
     */
    @Override
    public Key dequeue() {
        Key max = pq[1];
        exchange(1, high--);
        pq[high + 1] = null;
        sink(1);

        // 当栈大小小于数组的四分之一,将数组大小减半
        // 保证数组半满,下次需要改变数组大小之前仍能进行多次的 push() 和 pop()
        if (high > 0 && high <= (pq.length / 4)) {
            resize(pq.length / 2);
        }

        return max;
    }

    /**
     * 调正数组的大小
     *
     * @param max
     *            新的数组大小
     */
    private void resize(int max) {

        // 实现逻辑为将栈移动到另一个大小不同的数组中
        Key[] temp = (Key[])new Comparable[max];
        for (int i = 0; i <= high; i++) {
            temp[i] = pq[i];
        }

        pq = temp;
    }

    /**
     * 返回栈是否已满
     *
     * @return boolean
     */
    private boolean isFull() {
        return high >= pq.length - 1;
    }

    /**
     * 将较小的元素下沉
     * 
     * 原理:父节点与它的较大的子节点比较,如果小于这个较大的子节点, 那么他们交换位置.
     * 对于完全二叉树的节点 k 它的子节点为 2k 和 2k + 1
     * 
     * @param k
     *            元素指针
     */
    private void sink(int k) {
        while (2 * k <= high) {
            int j = 2 * k;
            if (j < high && less(j, j + 1)) {
                j++;
            }

            if (!less(k, j)) {
                break;
            }

            exchange(k, j);

            k = j;
        }
    }

    private boolean less(int i, int j) {
        return pq[i].compareTo(pq[j]) < 0;
    }

    private void exchange(int i, int j) {
        Key tmpKey = pq[i];
        pq[i] = pq[j];
        pq[j] = tmpKey;
    }

    /**
     * 返回队列是否为空
     *
     * @return 队列是否为空
     */
    @Override
    public boolean isEmpty() {
        return high == 0;
    }

    /**
     * 返回队列中元素的个数
     *
     * @return 队列中元素的个数
     */
    @Override
    public int size() {
        return 0;
    }

    @Override
    public Iterator<Key> iterator() {
        return new HeapIterator();
    }

    private class HeapIterator implements Iterator<Key> {

        private BiMaxPq<Key> copy;

        HeapIterator() {
            copy = new BiMaxPq<>(size());

            for (int i = 1; i <= high; i++) {
                copy.enqueue(pq[i]);
            }
        }

        @Override
        public boolean hasNext() {
            return !copy.isEmpty();
        }

        @Override
        public Key next() {

            if (!hasNext()) {
                throw new NoSuchElementException("已经没有元素可以迭代!");
            }

            return copy.dequeue();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException("不支持remove操作!");
        }
    }
}

最小元素优先出队

只需要调整 sink 和 swim 相关逻辑即可

    /**
     * 将较大的元素 上浮
     * <p>
     * 原理: 比对 当前元素 和 它的父级元素的大小;如果小于父级元素则与父级元素交换位置
     * 对于 完全二叉树 k 节点的 父节点为 k/2
     *
     * @param k
     *            指针位置
     */
    private void swim(int k) {
        while (k > 1 && gt(k / 2, k)) {
            exchange(k / 2, k);
            k = k / 2;
        }
    }

    /**
     * 将较大的元素下沉
     * <p>
     * 原理:父节点与它的较小的子节点比较,如果大于这个较小的子节点, 那么他们交换位置.
     * 对于完全二叉树的节点 k 它的子节点为 2k 和 2k + 1
     *
     * @param k
     *            元素指针
     */
    private void sink(int k) {
        while (2 * k <= high) {
            int j = 2 * k;
            if (j < high && gt(j, j + 1)) {
                j++;
            }

            if (!gt(k, j)) {
                break;
            }

            exchange(k, j);

            k = j;
        }
    }

    private boolean gt(int i, int j) {
        return pq[i].compareTo(pq[j]) > 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值