PriorityQueue / Обновление кучи - PullRequest
35 голосов
/ 03 апреля 2009

Есть ли в Java простой способ переоценки кучи после изменения приоритета объекта в PriorityQueue? Я не могу найти никаких признаков этого в Javadoc, но должен быть способ как-то это сделать, верно? В настоящее время я удаляю объект, затем повторно добавляю его, но это, очевидно, медленнее, чем запуск обновления в куче.

Ответы [ 6 ]

16 голосов
/ 03 апреля 2009

Возможно, вам придется реализовать такую ​​кучу самостоятельно. У вас должен быть некоторый указатель на положение элемента в куче, и некоторые методы, чтобы подтолкнуть элемент вверх или вниз, когда его приоритет изменился.

Несколько лет назад я написал такую ​​кучу как часть школьной работы. Нажатие элемента вверх или вниз является операцией 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());
        }
    }
}
8 голосов
/ 03 апреля 2009

PriorityQueue имеет метод heapify, который повторно сортирует всю кучу, метод fixUp, который продвигает элемент с более высоким приоритетом вверх, и метод fixDown, который перемещает элемент с более низким приоритетом вниз куча К сожалению, все эти методы являются закрытыми, поэтому вы не можете их использовать.

Я бы подумал об использовании шаблона Observer, чтобы содержащийся элемент мог сообщить очереди об изменении его приоритета, а затем очередь могла сделать что-то вроде fixUp или fixDown в зависимости от того, увеличился или уменьшился приоритет соответственно. .

3 голосов
/ 18 октября 2015

Это верно. PriorityQueue Java не предлагает метод для обновления приоритета, и кажется, что удаление занимает линейное время, так как оно не сохраняет объекты как ключи, как 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. Может потребоваться куча на основе требований.

3 голосов
/ 03 апреля 2009

Стандартные интерфейсы не обеспечивают возможность обновления. Вы используете пользовательский тип, который реализует это.

И ты прав; хотя сложность больших алгоритмов, использующих кучу, не изменяется при удалении и замене вершины кучи, их фактическое время выполнения может почти удвоиться. Я хотел бы видеть лучшую встроенную поддержку для использования кучи в стиле peek() и update().

2 голосов
/ 03 апреля 2009

В зависимости от реализации структуры данных, может не быть более быстрого пути. Большинство алгоритмов PQ / heap не предоставляют функцию обновления. Реализация Java может не отличаться. Обратите внимание, что хотя удаление / вставка делает код медленнее, вряд ли это приведет к коду с другой сложностью во время выполнения.

Редактировать : посмотрите на эту тему: Очередь приоритетов, которая позволяет эффективно обновлять приоритеты?

0 голосов
/ 24 июля 2018

К сожалению, приоритетная очередь JDK не предоставляет обновлений. Роберт Седжвик и Кевин Уэйн хорошо известны своими курсами алгоритмов в Принстоне, и они также написали Алгоритмы .

В этой превосходной книге они предоставляют свои собственные реализации для структур данных, включая обновляемые очереди приоритетов , такие как IndexMinPQ.java

Лицензия распространяется по GPLv3.

...