索引式优先队列(indexed priority queue)

为了达到O(ElogV)的效率,需要对普利姆算法进行eager实现。
如果我们用java来做,jdk当中的priorityQueue并不能满足我们的要求。
因为我们需要进行一个对索引元素降key的操作(decrease-key).

/** * 将索引所关联的key降到newKey * * @param index 索引 * @param newKey 新的key */
    public void decreaseKey(int index, E newKey) {
        if (index < 0 || index >= queue.length)
            throw new IndexOutOfBoundsException();
        if (newKey == null)
            throw new NullPointerException();
        if (!contains(index))
            throw new NoSuchElementException("指定的索引不存在!");
        if (this.comparator() != null) {
            if (this.comparator().compare((E) keys[index], newKey) <= 0)
                throw new IllegalArgumentException("指定的key必须小于原索引关联的key!");
        } else {
            Comparable<? super E> key = (Comparable<? super E>) getKeyOf(index);
            if (key.compareTo(newKey) <= 0)
                throw new IllegalArgumentException("指定的key必须小于原索引关联的key!");
        }

        keys[index] = newKey;
        siftUp(getPositionOf(index));
    }

由于需要知道优先队列中元素的索引以支持外部索引式访问,我们将对priorityQueue中的Queue建立索引,并将此索引当作二叉堆实际存储的元素,而原来的元素我们通过索引表来访问它.

    /** * 索引所关联的keys */
    private transient Object[] keys;
    /** * 建立一个从索引在二叉堆中的位置到索引的映射 */
    private transient int[] queue;

这里还有一个问题,就是在进行上浮(siftUp)和下沉(siftDown)时,需要对堆中的元素和位置进行互访,我们还需要建立一个索引到位置的倒排,或者索引到位置的一个映射.

    /** * 建立一个从索引到其在二叉堆中位置的映射,下标是关联key的索引 */
    private transient int[] postQueue;

这样我们就可以按下面的方式访问二叉堆中的索引、它在二叉堆中的位置、以及它关联的key:

/** * 返回给定位置的索引 * * @param position 二叉堆中的位置 * @return 给定位置的索引 */
    private int getIndexOf(int position) {
        return queue[position];
    }

    /** * 返回给定索引在二叉堆中的位置 * * @param index 索引 * @return 给定索引在二叉堆中的位置 */
    private int getPositionOf(int index) {
        return postQueue[index];
    }
    /** * 返回给定索引所关联的key * * @param index 索引 * @return 索引所关联的key */
    private E getKeyOf(int index) {
        return (E) keys[index];
    }

而交换元素时,需要先交换索引,然后再维护索引到位置的映射:

/** * 按位置交换 * * @param x 一个元素的位置 * @param y 另一个元素的位置 */
    private void exchangeByPosition(int x, int y) {
        int tmp = queue[x];
        queue[x] = queue[y];
        queue[y] = tmp;

        postQueue[queue[x]] = x;
        postQueue[queue[y]] = y;
    }

这样,我们的上浮操作看起来是这样的:

    private void siftUpComparable(int k) {
        Comparable<? super E> key = (Comparable<? super E>) keys[getIndexOf(k)];
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = keys[queue[parent]];
            if (key.compareTo((E) e) >= 0)
                break;
            exchangeByPosition(k, parent);
            k = parent;
        }
    }

而下沉操作就变成了这样:

private void siftDownUsingComparator(int k) {
        E x = getKeyOf(getIndexOf(k));
        int half = size >>> 1;
        while (k < half) {
            //假设作孩子是least
            int child = (k << 1) + 1;
            Object c = keys[child];
            int right = child + 1;
            //如果右孩子更小,那就和右孩子比较
            if (right < size &&
                    comparator.compare((E) c, getKeyOf(getIndexOf(right))) > 0)
                c = getKeyOf(getIndexOf(child = right));
            //当元素不大于它的所有孩子时停止
            if (comparator.compare(x, (E) c) <= 0)
                break;
            //否则交换元素和它最小的孩子
            exchangeByPosition(k, child);
            //继续下沉
            k = child;
        }
    }

既然加入了对元素的索引支持,那么入队操作就变成了这样:

    /** * 将index关联的key加入队列 * * @param index 索引 * @param key key * @return */
    public boolean offer(int index, E key) {
        if (index < 0)
            throw new IndexOutOfBoundsException();
        if (key == null)
            throw new NullPointerException();

        int last = size;
        //是否需要扩容
        if (last >= keys.length)
            grow(last + 1);
        size = last + 1;

        //首先将元素加入到队尾,然后从队尾上浮,直到满足堆的不变性

        //index到position的映射
        postQueue[index] = last;
        //position到index的映射
        queue[last] = index;
        //保存index关联的key
        keys[index] = key;
        if(last == 0)
            return true;
        //上浮
        siftUp(last);
        return true;
    }

相应的,出队操作也要修改:

/** * 返回最优先的元素并在队列中删除此元素 * * @return 最优先的元素, 如果为负,表示队列已空 */
    public int poll() {
        if (size == 0)
            return -1;
        int tail = --size;
        int head = getIndexOf(0);
        if(tail != 0){
            exchangeByPosition(0, tail);
            siftDown(0);
        }

        postQueue[head] = -1;
        //队尾的元素经过交换后就是之前的队头,现在可以删除了
        keys[getIndexOf(tail)] = null;
        queue[tail] = -1;
        return head;
    }

下面是对索引式优先队列的一个完整实现,支持最大优先队列和最小优先队列:


import java.util.*;

/** * Created by 浩然 on 4/19/15. * 索引式优先队列 * <p/> * 建立目的: * 在实现普利姆算法时,需要用优先队列来优化最轻边的查找. * 而jdk中的优先队列只满足lazy实现,如果要作eager实现,需实现一个decrease-key操作. * <p/> * 参考: * 1.普林斯顿大学 algorithms 4th edition. * 2.jdk priorityQueue. * <p/> * 说明: * 1.队头是最least的元素 * 2.可通过自定义Comparable或Comparator实现最大优先、最小优先队列. * 3.假设是最小优先队列,堆的不变性是指,任何插入、删除、出队、入队的操作都满足以下性质: * 任何一个父元素的key都不大于子元素的key * 而对于最大优先队列,则需满足任何一个父元素的key都不小于子元素的key */
public class IndexPriorityQueue<E> {
    private static final int DEFAULT_INITIAL_CAPACITY = 11;

    /** * 索引所关联的keys * 以O(1)的时间找出给定索引所关联的key */
    private transient Object[] keys;

    //建立下面两个辅助字段的目的:
    //1:以O(1)的时间找到给定索引的位置
    //2:以O(1)的时间找到给定位置的索引

    /** * 建立一个从索引在二叉堆中的位置到索引的映射,下标就是索引在二叉堆中的位置 */
    private transient int[] queue;

    /** * 建立一个从索引到其在二叉堆中位置的映射,下标是关联key的索引 */
    private transient int[] postQueue;

    private int size = 0;

    private final Comparator<? super E> comparator;

    public IndexPriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }

    public IndexPriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }

    public IndexPriorityQueue(int initialCapacity,
                              Comparator<? super E> comparator) {
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.keys = new Object[initialCapacity];
        this.queue = new int[initialCapacity];
        this.postQueue = new int[initialCapacity];
        for (int i = 0; i < postQueue.length; i++) {
            postQueue[i] = -1;
        }
        this.comparator = comparator;

    }

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /** * 扩容 * * @param minCapacity 所需的最小容量 */
    private void grow(int minCapacity) {
        int oldCapacity = keys.length;
        int newCapacity = oldCapacity + ((oldCapacity < 64) ?
                (oldCapacity + 2) :
                (oldCapacity >> 1));
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        keys = Arrays.copyOf(keys, newCapacity);
        queue = Arrays.copyOf(queue, newCapacity);
        postQueue = Arrays.copyOf(postQueue, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0)
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

    /** * 返回队列是否为空 * * @return 队列空则返回真,否则返回假 */
    public boolean isEmpty() {
        return size == 0;
    }

    /** * 将index关联的key加入队列 * * @param index 索引 * @param key key * @return */
    public boolean offer(int index, E key) {
        if (index < 0)
            throw new IndexOutOfBoundsException();
        if (key == null)
            throw new NullPointerException();

        int last = size;
        //是否需要扩容
        if (last >= keys.length)
            grow(last + 1);
        size = last + 1;

        //首先将元素加入到队尾,然后从队尾上浮,直到满足堆的不变性

        //index到position的映射
        postQueue[index] = last;
        //position到index的映射
        queue[last] = index;
        //保存index关联的key
        keys[index] = key;
        if(last == 0)
            return true;
        //上浮
        siftUp(last);
        return true;
    }

    /** * 返回最优先元素,并且不删除此元素 * * @return */
    public int peek() {
        if (size == 0)
            return -1;
        return getIndexOf(0);
    }

    /** * 队列中是否包含指定的元素 * * @param index 索引 * @return 如果指定的索引在队列中,返回真,否则返回假 */
    public boolean contains(int index) {
        return getPositionOf(index) != -1;
    }

    /** * 队列的长度 * * @return */
    public int size() {
        return size;
    }

    /** * 清空队列 */
    public void clear() {
        for (int i = 0; i < size; i++) {
            if (postQueue[i] < 0)
                continue;
            int index = getIndexOf(i);
            keys[index] = null;
            postQueue[index] = -1;
            queue[i] = -1;
        }
        size = 0;
    }

    /** * 返回最优先的元素并在队列中删除此元素 * * @return 最优先的元素, 如果为负,表示队列已空 */
    public int poll() {
        if (size == 0)
            return -1;
        int tail = --size;
        int head = getIndexOf(0);
        if(tail != 0){
            exchangeByPosition(0, tail);
            siftDown(0);
        }

        postQueue[head] = -1;
        //队尾的元素经过交换后就是之前的队头,现在可以删除了
        keys[getIndexOf(tail)] = null;
        queue[tail] = -1;
        return head;
    }

    private int remove(int index) {
        assert index >= 0 && index < size;
        if (!contains(index))
            return -1;

        int s = --size;
        int position = getPositionOf(index);
        //将堆中最后一个元素和要删除的元素交换
        exchangeByPosition(position, s);
        siftUp(position);
        siftDown(position);
        //help gc
        keys[index] = null;
        //标记在堆中已无此元素
        postQueue[index] = -1;
        return -1;
    }

    /** * 上浮元素 * * @param k 上浮开始的位置 */
    private void siftUp(int k) {
        if (comparator != null)
            siftUpUsingComparator(k);
        else
            siftUpComparable(k);
    }

    private void siftUpComparable(int k) {
        Comparable<? super E> key = (Comparable<? super E>) keys[getIndexOf(k)];
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = keys[queue[parent]];
            if (key.compareTo((E) e) >= 0)
                break;
            exchangeByPosition(k, parent);
            k = parent;
        }
    }

    private void siftUpUsingComparator(int k) {
        E key = (E) keys[getIndexOf(k)];
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = keys[queue[parent]];
            //如果key不小于其父节点的key,break
            if (comparator.compare(key, (E) e) >= 0)
                break;
            //否则交换current、parent处的元素
            exchangeByPosition(k, parent);
            //继续上浮
            k = parent;
        }
    }

    /** * * @param k */
    private void siftDown(int k) {
        if (comparator != null)
            siftDownUsingComparator(k);
        else
            siftDownComparable(k);
    }

    /** * 按位置交换 * * @param x 一个元素的位置 * @param y 另一个元素的位置 */
    private void exchangeByPosition(int x, int y) {
        int tmp = queue[x];
        queue[x] = queue[y];
        queue[y] = tmp;

        postQueue[queue[x]] = x;
        postQueue[queue[y]] = y;
    }

    private void siftDownComparable(int k) {
        Comparable<? super E> key = (Comparable<? super E>) getKeyOf(getIndexOf(k));
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = getKeyOf(getIndexOf(child));
            int right = child + 1;
            if (right < size &&
                    ((Comparable<? super E>) c).compareTo(getKeyOf(getIndexOf(right))) > 0)
                c = getKeyOf(getIndexOf(child = right));
            if (key.compareTo((E) c) <= 0)
                break;
            exchangeByPosition(k, child);
            k = child;
        }
    }

    private void siftDownUsingComparator(int k) {
        E x = getKeyOf(getIndexOf(k));
        int half = size >>> 1;
        while (k < half) {
            //假设作孩子是least
            int child = (k << 1) + 1;
            Object c = keys[child];
            int right = child + 1;
            //如果右孩子更小,那就和右孩子比较
            if (right < size &&
                    comparator.compare((E) c, getKeyOf(getIndexOf(right))) > 0)
                c = getKeyOf(getIndexOf(child = right));
            //当元素不大于它的所有孩子时停止
            if (comparator.compare(x, (E) c) <= 0)
                break;
            //否则交换元素和它最小的孩子
            exchangeByPosition(k, child);
            //继续下沉
            k = child;
        }
    }

    /** * 将索引所关联的key降到newKey * * @param index 索引 * @param newKey 新的key */
    public void decreaseKey(int index, E newKey) {
        if (index < 0 || index >= queue.length)
            throw new IndexOutOfBoundsException();
        if (newKey == null)
            throw new NullPointerException();
        if (!contains(index))
            throw new NoSuchElementException("指定的索引不存在!");
        if (this.comparator() != null) {
            if (this.comparator().compare((E) keys[index], newKey) <= 0)
                throw new IllegalArgumentException("指定的key必须小于原索引关联的key!");
        } else {
            Comparable<? super E> key = (Comparable<? super E>) getKeyOf(index);
            if (key.compareTo(newKey) <= 0)
                throw new IllegalArgumentException("指定的key必须小于原索引关联的key!");
        }

        keys[index] = newKey;
        siftUp(getPositionOf(index));
    }

    /** * 将索引所关联的key升到newKey * * @param index 索引 * @param newKey 新的key */
    public void increaseKey(int index, E newKey) {
        if (index < 0 || index >= queue.length)
            throw new IndexOutOfBoundsException();
        if (newKey == null)
            throw new NullPointerException();
        if (!contains(index))
            throw new NoSuchElementException("指定的索引不存在!");
        if (this.comparator() != null) {
            if (this.comparator().compare((E) keys[index], newKey) >= 0)
                throw new IllegalArgumentException("指定的key必须大于原索引关联的key!");
        } else {
            Comparable<? super E> key = (Comparable<? super E>) getKeyOf(index);
            if (key.compareTo(newKey) >= 0)
                throw new IllegalArgumentException("指定的key必须大于原索引关联的key!");
        }
        keys[index] = newKey;
        siftDown(getPositionOf(index));
    }

    /** * 调整队列以保证堆的不变性 */
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i);
    }

    public Comparator<? super E> comparator() {
        return comparator;

    }

    /** * 返回给定位置的索引 * * @param position 二叉堆中的位置 * @return 给定位置的索引 */
    private int getIndexOf(int position) {
        return queue[position];
    }

    /** * 返回给定索引在二叉堆中的位置 * * @param index 索引 * @return 给定索引在二叉堆中的位置 */
    private int getPositionOf(int index) {
        return postQueue[index];
    }
    /** * 返回给定索引所关联的key * * @param index 索引 * @return 索引所关联的key */
    private E getKeyOf(int index) {
        return (E) keys[index];
    }
}

你可能感兴趣的:(index)