PriorityQueue /堆更新

时间:2009-04-03 16:58:27

标签: java heap priority-queue

一旦PriorityQueue中对象的优先级发生变化,Java是否有一种简单的方法来重新评估堆?我在Javadoc中找不到任何迹象,但必须有办法以某种方式做到这一点,对吗?我正在删除该对象然后重新添加它,但这显然比在堆上运行更新要慢。

7 个答案:

答案 0 :(得分:16)

您可能需要自己实现这样的堆。您需要对堆中项的位置有一些处理,以及在优先级发生更改时向上或向下推送项的一些方法。

几年前,我写了这么一堆作为学校工作的一部分。向上或向下推动项目是O(log N)操作。我将以下代码作为公共域发布,因此您可以以任何方式使用它。 (您可能希望改进此类,以便代替抽象的isGreaterOrEqual方法,排序顺序将依赖于Java的Comparator和Comparable接口,并且还会使该类使用泛型。)

import java.util.*;

public abstract class Heap {

    private List heap;

    public Heap() {
        heap = new ArrayList();
    }

    public void push(Object obj) {
        heap.add(obj);
        pushUp(heap.size()-1);
    }

    public Object pop() {
        if (heap.size() > 0) {
            swap(0, heap.size()-1);
            Object result = heap.remove(heap.size()-1);
            pushDown(0);
            return result;
        } else {
            return null;
        }
    }

    public Object getFirst() {
        return heap.get(0);
    }

    public Object get(int index) {
        return heap.get(index);
    }

    public int size() {
        return heap.size();
    }

    protected abstract boolean isGreaterOrEqual(int first, int last);

    protected int parent(int i) {
        return (i - 1) / 2;
    }

    protected int left(int i) {
        return 2 * i + 1;
    }

    protected int right(int i) {
        return 2 * i + 2;
    }

    protected void swap(int i, int j) {
        Object tmp = heap.get(i);
        heap.set(i, heap.get(j));
        heap.set(j, tmp);
    }

    public void pushDown(int i) {
        int left = left(i);
        int right = right(i);
        int largest = i;

        if (left < heap.size() && !isGreaterOrEqual(largest, left)) {
            largest = left;
        }
        if (right < heap.size() && !isGreaterOrEqual(largest, right)) {
            largest = right;
        }

        if (largest != i) {
            swap(largest, i);
            pushDown(largest);
        }
    }

    public void pushUp(int i) {
        while (i > 0 && !isGreaterOrEqual(parent(i), i)) {
            swap(parent(i), i);
            i = parent(i);
        }
    }

    public String toString() {
        StringBuffer s = new StringBuffer("Heap:\n");
        int rowStart = 0;
        int rowSize = 1;
        for (int i = 0; i < heap.size(); i++) {
            if (i == rowStart+rowSize) {
                s.append('\n');
                rowStart = i;
                rowSize *= 2;
            }
            s.append(get(i));
            s.append(" ");
        }
        return s.toString();
    }

    public static void main(String[] args){
        Heap h = new Heap() {
            protected boolean isGreaterOrEqual(int first, int last) {
                return ((Integer)get(first)).intValue() >= ((Integer)get(last)).intValue();
            }
        };

        for (int i = 0; i < 100; i++) {
            h.push(new Integer((int)(100 * Math.random())));
        }

        System.out.println(h+"\n");

        while (h.size() > 0) {
            System.out.println(h.pop());
        }
    }
}

答案 1 :(得分:8)

PriorityQueue具有重新排序整个堆的heapify方法,fixUp方法,用于在堆上提升优先级较高的元素,以及推送fixDown方法。堆中的优先级较低的元素。不幸的是,所有这些方法都是私有的,所以你不能使用它们。

我会考虑使用Observer模式,以便包含的元素可以告诉Queue它的优先级已经改变,然后Queue可以执行类似fixUpfixDown的操作,具体取决于优先级是否优先级分别增加或减少。

答案 2 :(得分:3)

标准接口不提供更新功能。您已使用实现此的自定义类型。

你是对的;虽然在删除和替换堆顶部时,使用堆的算法的大O复杂性不会改变,但它们的实际运行时间几乎可以翻倍。我希望看到更好的内置支持peek()update()样式的堆使用。

答案 3 :(得分:3)

那是对的。 Java的PriorityQueue没有提供更新优先级的方法,并且删除似乎是线性时间,因为它不像Map那样将对象存储为键。它实际上多次接受同一个对象。

我还想让PQ提供更新操作。以下是使用泛型的示例代码。任何可比较的类都可以与它一起使用。

class PriorityQueue<E extends Comparable<E>> {
    List<E> heap = new ArrayList<E>();
    Map<E, Integer> map = new HashMap<E, Integer>();

    void insert(E e) {
        heap.add(e);
        map.put(e, heap.size() - 1);
        bubbleUp(heap.size() - 1);
    }

    E deleteMax() {
        if(heap.size() == 0)
            return null;
        E result = heap.remove(0);
        map.remove(result);
        heapify(0);
        return result;
    }

    E getMin() {
        if(heap.size() == 0)
            return null;
        return heap.get(0);
    }

    void update(E oldObject, E newObject) {
        int index = map.get(oldObject);
        heap.set(index, newObject);
        bubbleUp(index);
    }

    private void bubbleUp(int cur) {
        while(cur > 0 && heap.get(parent(cur)).compareTo(heap.get(cur)) < 0) {
            swap(cur, parent(cur));
            cur = parent(cur);
        }
    }

    private void swap(int i, int j) {
        map.put(heap.get(i), map.get(heap.get(j)));
        map.put(heap.get(j), map.get(heap.get(i)));
        E temp = heap.get(i);
        heap.set(i, heap.get(j));
        heap.set(j, temp);
    }

    private void heapify(int index) {
        if(left(index) >= heap.size())
            return;
        int bigIndex = index;
        if(heap.get(bigIndex).compareTo(heap.get(left(index))) < 0)
            bigIndex = left(index);
        if(right(index) < heap.size() && heap.get(bigIndex).compareTo(heap.get(right(index))) < 0)
            bigIndex = right(index);
        if(bigIndex != index) {
            swap(bigIndex, index);
            heapify(bigIndex);
        }
    }

    private int parent(int i) {
        return (i - 1) / 2;
    }

    private int left(int i) {
        return 2*i + 1;
    }

    private int right(int i) {
        return 2*i + 2;
    }
}

这里在更新时,我只是增加优先级(对于我的实现)而且它正在使用MaxHeap,所以我正在做bubbleUp。可能需要根据需求进行堆积。

答案 4 :(得分:2)

根据数据结构的实现,可能没有更快的方法。大多数PQ /堆算法不提供更新功能。 Java实现可能没有任何不同。请注意,虽然删除/插入会使代码变慢,但不太可能导致代码具有不同的运行时复杂性。

修改:看一下这个主题:A priority queue which allows efficient priority update?

答案 5 :(得分:0)

不幸的是,JDK的Priority Queue不提供更新。 Robert Sedgewick和Kevin Wayne以其在普林斯顿的算法课程而闻名,他们还撰写了Algorithms

在这本出色的书中,他们提供了自己的数据结构实现,包括可更新的priority queues,例如IndexMinPQ.java

根据GPLv3许可。

答案 6 :(得分:0)

你需要自己实现它。但你不必花哨。在 Java 的 ThisWorkbook.Worksheet.Range 实现中删除堆项目的实际大量时间实际上是 remove(Object),因为它必须迭代整个列表以找到特定对象的索引。如果您实现自己的数据结构,您可以告诉每个对象在数组中的位置,即使您的实现没有什么特别之处,它也会胜过 Java,因为每个对象都知道它在数组中的位置。

存储这些信息,您只需执行经典的删除和添加新项目,您就会大大击败 Java。

更新例程只是在特定索引上调用 heapify。它节省了 heapify 调用和一些常量操作。这里的大部分优化是 Java 的实际 indexOf() 无法存储索引。所以 PriorityQueue 在该数据结构中实际上是一个非常昂贵的操作。因为您将不得不在列表中找到该对象。这个特殊的类将 remove(Object) 花费的时间减少到几乎为零。尽管它要求您对放入堆中的项目实施 PriorityQueue

Heap.Indexed