Есть ли у Java простой способ переоценить кучу после изменения приоритета объекта в PriorityQueue? Я не могу найти никаких признаков этого в Javadoc
, но должен быть способ сделать это как-то, не так ли? В настоящее время я удаляю объект, а затем повторно добавляю его, но это явно медленнее, чем запуск обновления в куче.
Обновление PriorityQueue/Heap
Ответ 1
Возможно, вам придется реализовать такую кучу самостоятельно. Вам нужно иметь некоторую ручку для позиции элемента в куче, а также некоторые методы для перемещения элемента вверх или вниз, когда его приоритет изменился.
Несколько лет назад я написал такую кучу в рамках школьной работы. Нажатие элемента вверх или вниз - это операция O (log N). Я освобождаю следующий код как общедоступный, поэтому вы можете использовать его любым способом. (Возможно, вы захотите улучшить этот класс, чтобы вместо абстрактного метода isGreaterOrEqual порядок сортировки опирался на интерфейсы Java Comparator и Comparable, а также позволял классу использовать generics.)
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());
}
}
}
Ответ 2
PriorityQueue имеет метод heapify
, который перебирает всю кучу, метод fixUp
, который продвигает элемент с более высоким приоритетом до кучи и метод fixDown
, который толкает элемент с более низким приоритетом вниз куча. К сожалению, все эти методы являются частными, поэтому вы не можете их использовать.
Я бы рассмотрел использование шаблона Observer, чтобы содержащийся элемент мог сообщить Очередь, что его приоритет был изменен, а Queue может затем сделать что-то вроде fixUp
или fixDown
в зависимости от того, увеличен или уменьшен приоритет соответственно.
Ответ 3
Стандартные интерфейсы не предоставляют возможности обновления. Вы используете специальный тип, который реализует это.
И ты прав; хотя большая сложность алгоритмов, использующих кучу, не изменяется при удалении и замене вершины кучи, их фактическое время работы может почти удвоиться. Я бы хотел увидеть лучшую встроенную поддержку стиля peek()
и update()
использования кучи.
Ответ 4
Это правильно. PriorityQueue
Java не предлагает метод обновления приоритета, и кажется, что удаление занимает линейное время, поскольку оно не хранит объекты в виде ключей, как это делает 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. Возможно, понадобится heapify на основе требования.
Ответ 5
В зависимости от реализации структуры данных может быть не так, как быстрее. Большинство алгоритмов PQ/heap не обеспечивают функцию обновления. Реализация Java может отличаться. Обратите внимание, что хотя remove/insert делает код медленнее, вряд ли это приведет к созданию кода с другой сложностью выполнения.
Изменить: посмотрите этот поток: Очередь приоритетов, которая позволяет эффективно обновлять приоритет?