【问题标题】:PriorityQueue/Heap UpdatePriorityQueue/堆更新
【发布时间】:2010-10-17 10:04:27
【问题描述】:

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

【问题讨论】:

  • 我很好奇会得到什么样的答案;我以前遇到过这种情况,似乎没有一个简单的答案。我怀疑你能比 O(log n) 做得更好。 remove(Object) 方法是您当前方法的瓶颈,它在时间上是线性的。
  • 我通常只是添加新项目,不删除这很慢。为了使代码正确,我保留了一个单独的数组或映射,其中包含应该删除的元素,因此当它们出现时,我可以忽略它们。

标签: java heap priority-queue


【解决方案1】:

您需要自己实现它。但你不必花哨。在Java 的remove(Object) 实现中删除堆项目的实际大量时间实际上是indexOf(),因为它必须遍历整个列表才能找到特定对象的索引。如果您实现自己的数据结构,您可以告诉每个对象在数组中的位置,即使您的实现并不花哨,它也会胜过 Java,因为每个对象都会知道它在数组中的位置。

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

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

import java.util.Arrays;

public class Heap<T extends Heap.Indexed<T>> {

    private Indexed[] heap;
    private int length = 0;

    public Heap() {
        heap = new Indexed[12];
    }

    private void ensureCapacity() {
        if (length > heap.length) {
            heap = Arrays.copyOf(heap, length * 2);
        }
    }

    public void add(T obj) {
        int index = length++;
        ensureCapacity();
        obj.setIndex(index);
        heap[index] = obj;
        heapify(index);
    }

    public T removeAt(int index) {
        T result = get(index);
        length -= 1;
        if ((length > 0) && (index != length)) {
            swap(index, length);
            heapify(index);
        }
        result.setIndex(-1);
        heap[length] = null;
        return result;
    }

    public T remove(T obj) {
        int index = obj.getIndex();
        if (index == -1) {
            return null;
        }
        return removeAt(index);
    }

    public void update(T obj) {
        int index = obj.getIndex();
        obj.setIndex(-1);
        if (index == -1) {
            return;
        }
        heapify(index);
    }

    public T poll() {
        if (length == 0) {
            return null;
        }
        return removeAt(0);
    }

    public T peek() {
        return get(0);
    }

    public T get(int index) {
        return (T) heap[index];
    }

    public int size() {
        return length;
    }

    protected boolean compare(int first, int last) {
        return get(first).compareTo(get(last)) > -1;
    }

    protected void swap(int i, int j) {
        T tmp = (T) heap[i];
        heap[i] = (T) heap[j];
        heap[j] = tmp;
        heap[i].setIndex(i);
        heap[j].setIndex(j);
    }

    public void heapify(int index) {
        int parent = (index - 1) / 2;
        if (index > 0 && !compare(parent, index)) {
            swap(parent, index);
            heapify(parent);
            return;
        }
        int left = (index << 1) + 1;
        int right = left + 1;
        int largest = index;

        if (left < length && !compare(largest, left)) {
            largest = left;
        }
        if (right < length && !compare(largest, right)) {
            largest = right;
        }

        if (largest != index) {
            swap(largest, index);
            heapify(largest);
        }
    }

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

    public void clear() {
        this.length = 0;
        Arrays.fill(heap, null);
    }

    public interface Indexed<I extends Heap.Indexed> extends Comparable<I> {

        int getIndex();

        void setIndex(int index);
    }

}

【讨论】:

    【解决方案2】:

    很遗憾,JDK 的优先队列不提供更新。 Robert Sedgewick 和 Kevin Wayne 以在普林斯顿的算法课程而闻名,他们还写了Algorithms

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

    根据 GPLv3 获得许可。

    【讨论】:

      【解决方案3】:

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

      我也想做PQ提供更新操作。这是使用泛型的示例代码。任何 Comparable 类都可以与它一起使用。

      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。可能需要根据需求进行堆放。

      【讨论】:

      • 这段代码有两个问题: 1.当从deleteMax中的heap中删除一个项目时,map的值现在是错误的; 2.swap 错误地交换了map 的值——你需要使用一个临时变量。因此,它根本无法以当前的形式工作。
      【解决方案4】:

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

      我会考虑使用观察者模式,以便包含的元素可以告诉队列它的优先级已经改变,然后队列可以根据优先级分别增加或减少来执行fixUpfixDown 之类的操作.

      【讨论】:

      • 你是说Java.util.priorotyqueue有那些方法吗?我在 javadoc 中没有看到它们
      • @Sridhar-Sarnobat 就像亚当说的那样,它们是私有的,所以它们不会出现在 java 文档中。
      • 为什么 Java 不公开 heapify 方法呢?它会更加可定制和用户友好吗?公开有什么坏处?
      • heapify() 在您执行 removeIf(..) 时被调用。因此,如果您不介意这种方法的 O(n) 工作量,您可以调用 removeIf(x -&gt; false),它会在删除任何内容后隐式调用 heapify()
      【解决方案5】:

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

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

      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());
              }
          }
      }
      

      【讨论】:

      • 这正是我正在寻找的。我只是暂时不想实现这个,但需要使用它。我可能会很快发布改进的版本(正如你提到的,我想使用泛型和比较器)
      • PushDown 和 PushUp 是不够的,这样的事情需要一个完整的 heapify 函数,涉及更多的步骤。上面的代码绝对可以违反堆属性。
      【解决方案6】:

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

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

      【讨论】:

      • +1 用于更新功能。而且我还希望标准的 java Queue 或 Dequeue 对高数据量有更好的实现。自制一个快 30% 的实现真的很容易。
      • 12 年后,仍然没有更好的内置支持。
      【解决方案7】:

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

      编辑:看看这个帖子:A priority queue which allows efficient priority update?

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-04
        • 2010-12-24
        • 2020-08-09
        • 1970-01-01
        相关资源
        最近更新 更多