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

来源:互联网 发布:spring 源码阅读建议 编辑:程序博客网 时间:2024/05/22 11:52

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

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
原创粉丝点击