Проблема в необычном decreaseKey у бинарной кучи
Задача:
insert x — вставить целое число x в кучу;
getMin — вывести значение минимального элемента в куче (гарантируется, что к этому моменту куча не пуста);
extractMin — удалить минимальный элемент из кучи, выводить его не нужно (гарантируется, что к этому моменту куча не пуста);
decreaseKey i Δ— уменьшить число, вставленное на i-м запросе, на целое число Δ ≥ 0
(гарантируется, что i-й запрос был осуществлён ранее, являлся запросом добавления,
а добавленное на этом шаге число всё ещё лежит в куче).
Обратите внимание, число i равно номеру запроса среди всех запросов, а не только среди запросов добавления!
У меня возникла трудность с методом decreaseKey: по обычному ключу реализация ясна, а в данном случае необходимо уменьшать ключ не по вершине, а по номеру запроса. Мне пришла идея сделать две хеш-мапы: номер запроса - вершина (mapQV) и вершина - номер запроса (mapVQ).
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
public class Main {
static HashMap<Integer, Integer> mapQV = new HashMap<>();
static HashMap<Integer, Integer> mapVQ = new HashMap<>();
private static ArrayList<Long> Heap = new ArrayList<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int q = scanner.nextInt();
String[] str = new String[q];
for (int i = 0; i < q; i++) {
str[i] = scanner.next();
switch (str[i]) {
case "insert" -> {
int temp = scanner.nextInt();
int v = (int) insertX(Heap, temp);
mapQV.put(i, v);
mapVQ.put(v, i);
}
case "decreaseKey" -> {
int temp1 = scanner.nextInt();
int temp2 = scanner.nextInt();
decreaseKey(Heap, temp1 - 1, temp2);
}
case "extractMin" -> {
extractMin(Heap);
}
default -> {
System.out.println(getMin(Heap));
}
}
}
}
public static int siftUp(ArrayList<Long> Heap, int v) {
if (v == 0) {
return v;
}
int p = (v + 1) / 2 - 1;
if (Heap.get(v) < Heap.get(p)) {
Heap.set(v, -Heap.get(v) - Heap.get(p));
Heap.set(p, -Heap.get(v) - Heap.get(p));
Heap.set(v, -Heap.get(v) - Heap.get(p));
int q1 = mapVQ.get(p);
mapQV.remove(q1);
mapVQ.remove(p);
mapQV.put(q1, v);
mapVQ.put(v, q1);
return siftUp(Heap, p);
}
return v;
}
public static int siftDown(ArrayList<Long> Heap, int v) {
int n = Heap.size();
if (2 * v + 1 >= n) {
return v;
}
int u = 2 * v + 1;
if (2 * v + 2 <= n - 1 && Heap.get(2 * v + 2) < Heap.get(2 * v + 1)) {
u = 2 * v + 2;
}
if (Heap.get(u) < Heap.get(v)) {
Heap.set(u, -Heap.get(u) - Heap.get(v));
Heap.set(v, -Heap.get(u) - Heap.get(v));
Heap.set(u, -Heap.get(u) - Heap.get(v));
int q1 = mapVQ.get(u);
int q2 = mapVQ.get(v);
mapQV.remove(q1);
mapVQ.remove(u);
mapQV.remove(q2);
mapVQ.remove(v);
mapQV.put(q1, v);
mapVQ.put(v, q1);
mapQV.put(q2, u);
mapVQ.put(u, q2);
return siftDown(Heap, u);
}
return v;
}
public static long getMin(ArrayList<Long> Heap) {
return (Heap.get(0));
}
public static int extractMin(ArrayList<Long> Heap) {
int n = Heap.size();
int q1 = mapVQ.get(0);
int q2 = mapVQ.get(n - 1);
Heap.set(0, -Heap.get(0) - Heap.get(n - 1));
Heap.set(n - 1, -Heap.get(0) - Heap.get(n - 1));
Heap.set(0, -Heap.get(0) - Heap.get(n - 1));
mapQV.remove(q1);
mapQV.put(q1, n - 1);
mapVQ.put(n - 1, q1);
mapQV.remove(q2);
mapQV.put(q2, 0);
mapVQ.put(0, q2);
Heap.remove(n - 1);
mapQV.remove(q1);
mapVQ.remove(n - 1);
Heap.trimToSize();
return siftDown(Heap, 0);
}
public static long insertX(ArrayList<Long> Heap, long x) {
Heap.add(x);
return siftUp(Heap, Heap.size() - 1);
}
public static void decreaseKey(ArrayList<Long> Heap, int q, int delta) {
Heap.set(mapQV.get(q), Heap.get(mapQV.get(q)) - delta);
siftUp(Heap, mapQV.get(q));
}
}
Моя идея в том, чтобы в decreaseKey, пользуясь двумя map, найти по значению вершину, и уже для нее реализовать привычный метод, но Я не могу реализовать правильное сохранение пар мап во время их изменения вследствие других методов, таких как: insert, extractMin (и, соответственно, в siftUp/siftDown). Приведу тест:
14
insert 6
insert 45
decreaseKey 2 15
insert 20
insert 7
extractMin
getMin
insert 5
decreaseKey 5 5
getMin
insert 18
insert 22
extractMin
getMin
Выводит
7
2
А третьего вывода getMin нет (а должен быть), так как возникает ссылка на null в парах в map. Буду благодарен, если вы сможете изменить код так, чтобы ошибки в парах не было.
Ответы (1 шт):
Массив positions индексируется номером запроса и хранит позицию элемента в куче heap. Куча heap хранит, помимо полезной нагрузки, номер запроса (pos). На псевдокоде операции с кучей будут выглядеть как:
def append_last(payload, pos):
""" Помещает (payload, pos) в конец кучи """
i = heap.size
heap.size += 1 # добавляем новую ячейку
heap[i].payload = payload
heap[i].pos = pos
positions[pos] = i
def remove_last():
""" Убирает последнюю ячейку из кучи """
i = heap.size - 1
positions[heap[i].pos] = NO_INDEX
heap.size -= 1 # убираем последнюю ячейку
def swap(i, j):
""" Меняет местами heap[i], heap[j] """
temp = heap[i]
heap[i] = heap[j]
heap[j] = temp
positions[heap[i].pos] = i
positions[heap[j].pos] = j
Ваша задача менять кучу только этими тремя методами.
Массив positions отслеживает перемещение объектов по куче. Массив можно заменить на мапу, это ничего не меняет.