每日一省————使用二叉堆实现优先队列

22 篇文章 0 订阅

今天,本人主要想复习优先队列这种数据结构。虽然标题号称“每日一省“,但是工作的人身不由己,已经很多天没有更新了。哈哈,废话不多说,直接进入正题吧。

1 优先队列的概念:

优先队列是一种抽象的数据结构,队列中的每个元素都有一个优先级值。优先级值用来表示该元素的出对的先后次序。
优先队列是至少允许插入操作和删除最小(或者最大)元素这两种操作的数据结构。通常,优先队列通过二叉堆来实现。

2 二叉堆的概念:

堆是一棵被完全填满的二叉树,底层例外,底层上的元素从左到右依次填入。如果使用数组表示二叉堆,那么对于数组上的任意位置i上的元素,其左子元素索引是2i,右子元素的索引是(2i +1),其父节点索引是[i/2]。
堆的性质,在堆中,每个元素都要保证大于等于另两个特定位置的元素。所以,在堆有序的二叉树中,每个节点又都小于等于它的父节点。所以,根节点是堆有序的二叉树中的最大节点。
当然,对的性质也可以反过来,也即:每个元素都要保证小于等于另两个特定位置的元素。所以,在堆有序的二叉树中,每个节点又都大于等于它的父节点。所以,根节点是堆有序的二叉树中的最小节点。

3 使用二叉堆实现的优先队列的代码示例


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

public class MaxFirstQueue<T> implements Iterable<T> {

    private T[] queue; // 本列子中优先队列的底层实现是二叉堆,而二叉堆又用数组来表示
    private int size = 0; // 表示队列中的元素个数,注意不是表示数组的容量
    private Comparator<T> comparator; // 可选的比较器,可以作为该类的构造函数参数传入

    public MaxFirstQueue(int max) {
        queue = (T[]) new Comparable[max + 1];
    }

    public MaxFirstQueue() {
        this(1);
    }

    public MaxFirstQueue(int initCapacity, Comparator<T> comparator) {
        this.comparator = comparator;
        queue = (T[]) new Object[initCapacity + 1];
        size = 0;
    }

    public MaxFirstQueue(Comparator<T> comparator) {
        this(1, comparator);
    }

    public MaxFirstQueue(T[] values) {
        size = values.length;
        queue = (T[]) new Object[values.length + 1];
        for (int i = 0; i < size; i++) {
            // 用数组表示二叉堆,数组索引为0的位置不存放元素,该位置元素值永远为空
            queue[i + 1] = values[i];
        }
        for (int k = size / 2; k >= 1; k--) {
            sinkDown(k);
        }
        assert isValidHeap();
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    /*
     * 判断数组queue中的所有元素构成的二叉堆是否满足二叉堆的基本性质,也即父节点元素最大, 然后每个子节点比其更下面的子节点大
     */
    private boolean isValidHeap() {
        return isValidSubHeap(1);
    }

    /*
     * 判断以queue[k]位置为元素的子堆是否满足二叉堆的基本性质:也即父节点(父节点为queue[k])元素最大,
     * 然后每个子节点比其更下面的子节点大
     */
    private boolean isValidSubHeap(int k) {
        if (k > size) {
            return true;
        }
        int left = 2 * k, right = 2 * k + 1;
        if (left <= size && less(k, left)) {
            return false;
        }
        if (right <= size && less(k, right)) {
            return false;
        }
        return isValidSubHeap(left) && isValidSubHeap(right);
    }

    /**
     * 取出最大值
     */
    public T max() {
        if (isEmpty()) {
            throw new NoSuchElementException("每次可以取出最大元素的优先队列已经为空,没有任何元素存储其中了");
        }
        return queue[1];
    }

    /**
     * 插入元素
     */
    public void add(T value) {
        if (size >= queue.length - 1) {
            resize(2 * queue.length);
        }
        queue[++size] = value;
        swimUP(size);
        assert isValidHeap();
    }

    /**
     * 删除一个元素后,实际是把删除的元素的放到了索引为size的位置(再把该位置元素值设为null), 该位置已经不能算做是二叉堆的一部分了。
     * 二叉堆的大小变为size--
     * 
     * 下面的三行代码可以合写为exchange(1, size--), queue[size + 1] = null,这三行代码如下:
     * exchange(1, size); queue[size] = null; size--;
     */
    public T deleteMax() {
        if (isEmpty()) {
            throw new NoSuchElementException("每次可以取出最大元素的优先队列已经为空,没有任何元素存储其中了");
        }
        T value = queue[1];
        exchange(1, size);
        queue[size] = null;
        size--;
        sinkDown(1);
        if ((size > 0) && (size == (queue.length - 1) / 4)) {
            resize(queue.length / 2);
        }
        assert isValidHeap();
        return value;
    }

    /*
     * 调整底层数组的大小:具体做法是创建指定大小的临时数组,然后把queue中的元素移动到这个临时数组的相应位置,
     * 最后再将这个临时数组设置为优先队列的底层实现。
     */
    private void resize(int capacity) {
        assert capacity > size;
        T[] temp = (T[]) new Object[capacity];
        for (int i = 1; i <= size; i++) {
            temp[i] = queue[i];
        }
        queue = temp;
    }

    private boolean less(int i, int j) {
        if (comparator == null) {
            return ((Comparable<T>) queue[i]).compareTo(queue[j]) < 0;
        } else {
            return comparator.compare(queue[i], queue[j]) < 0;
        }
    }

    private void exchange(int i, int j) {
        T temp = queue[i];
        queue[i] = queue[j];
        queue[j] = temp;
    }

    /*
     * 将元素值大于父节点的子节点与父节点交换位置,保持堆有序。交换位置后,原来的子节点可能仍然比 更上层的父节点大,
     * 所以整个过程需要递归进行。这样一来,原来的子节点 有可能升级为层级更高的父节点,类似于一个物体从湖底往上浮直到达到其重力与浮力相平衡的过程。
     */
    private void swimUP(int k) {
        while (k > 1 && less(k / 2, k)) {
            exchange(k, k / 2);
            k = k / 2;
        }
    }

    /*
     * 将元素值小于子节点的父节点与子节点交换位置,交换位置后, 原来的父节点仍然有可能比其下面的子节点小,
     * 所以还需要继续进行类相同的操作,以便保持堆的有序性。所以整个过程递归进行。
     * 这类似于一个物体从湖面下沉到距离湖底的某个位置,直到达到其重力与浮力相平衡为止。
     */
    private void sinkDown(int k) {
        while (2 * k <= size) {
            int j = 2 * k;
            if (j < size && less(j, j + 1)) {
                j++;
            }
            if (!less(k, j)) {
                break;
            }
            exchange(k, j);
            k = j;
        }
    }

    @Override
    public Iterator<T> iterator() {
        return new MaxFirstQueueIterator();
    }

    /**
     * 用于遍历优先队列中元素的迭代器的具体实现:
     */
    private class MaxFirstQueueIterator implements Iterator<T> {

        /*
         * 相当于被迭代的优先队列的一个副本,遍历优先队列中的元素其实遍历的是其副本,
         * 所以,在多线程环境下,并发的增加、删除、遍历元素可能会导致数据错乱。
         */
        private MaxFirstQueue<T> copy;

        public MaxFirstQueueIterator() {
            if (comparator == null) {
                copy = new MaxFirstQueue<T>(size());
            } else {
                copy = new MaxFirstQueue<T>(size(), comparator);
            }
            for (int i = 1; i <= size; i++)
                copy.add(queue[i]);
        }

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

        public void remove() {
            throw new UnsupportedOperationException("迭代器不支持移除操作");
        }

        public T next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return copy.deleteMax();
        }
    }

    /**
     * 主方法:包含有测试代码
     */
    public static void main(String[] args) {
        MaxFirstQueue<String> pq = new MaxFirstQueue<String>();
        pq.add("S");
        pq.add("T");
        System.out.println("====================================================");
        for (String e : pq) {
            System.out.println(e);
        }
        pq.add("C");
        pq.add("A");
        pq.add("M");
        pq.add("B");
        System.out.println("====================================================");
        for (String e : pq) {
            System.out.println(e);
        }
        System.out.println("====================================================");
        Iterator<String> iterator = pq.iterator();
        iterator.forEachRemaining(t -> System.out.print("-" + t + "-"));
        System.out.println();
        System.out.println("====================================================");
        try {
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
            System.out.println(pq.deleteMax());
        } catch (Exception e) {
            System.out.println("队列为空,大小为" + pq.size);
        }

        pq.add("C");
        pq.add("A");
        pq.add("M");
        pq.add("B");
        System.out.println("队列大小为" + pq.size);

    }

}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值