Проблема в необычном 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 шт):

Автор решения: Stanislav Volodarskiy

Массив 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 отслеживает перемещение объектов по куче. Массив можно заменить на мапу, это ничего не меняет.

→ Ссылка