Можно ли реализовать двоичную кучу, которая является как максимальной, так и минимальной кучей?

Я пытаюсь реализовать двоичную кучу (приоритетную очередь), которая имеет возможности как минимальной, так и максимальной кучи. Он должен иметь методы insert(value), extractMin() и extractMax(). Методы извлечения удаляют значение из кучи и возвращают значение.

Первоначально я использовал два массива, называемых minHeap и maxHeap, один для хранения данных в структуре минимальной кучи, а другой для хранения тех же данных в структуре максимальной кучи. Поэтому, когда я вызываю extractMin(), он удаляет и возвращает значение из minHeap. Затем я также должен удалить это значение из maxHeap (и наоборот, если я вызывал extractMax()), чтобы сохранить идентичный набор данных в обеих кучах. И из-за свойства порядка кучи я гарантированно найду это значение в листьях другой кучи. Поиск этого значения в другой куче приводит к временной сложности O(n) или, точнее, O(n/2), поскольку я буду искать только листья. Не говоря уже о том, что методы percolatingDown() и percolatingUp() для восстановления кучи после удаления значений уже равны O(log n); так что в целом методы извлечения будут O (n). Проблема в том, что мне нужно, чтобы методы извлечения были O (log n).

Есть ли лучший способ сделать это?

Я тоже думал об этой идее, но сначала хотел узнать, что вы все думаете.

Я только что закончил кодировать «среднюю кучу», поместив меньшую половину данных в максимальную кучу, а большую половину в минимальную кучу. С помощью этой структуры я могу легко получить медиану заданного набора значений. И я думал об использовании аналогичной структуры размещения меньшей половины данных в минимальной куче и большей половине в максимальной куче и использовании среднего (а не медианы) всех значений в качестве решающего фактора того, является ли чтобы поместить значение в максимальную или минимальную кучу при вызове insert(value). Я думаю, что это может сработать, поскольку методы извлечения останутся O (log n).


person David Velasquez    schedule 23.07.2015    source источник
comment
Почему бы вместо этого не использовать бинарное дерево? Методы extract по-прежнему будут O(log n), поскольку это просто самый левый и самый правый узел для extractMin() и extractMax() соответственно.   -  person M. Shaw    schedule 23.07.2015
comment
@ M.Shaw Я мог бы это сделать, но это задание, и моему профессору нужна куча.   -  person David Velasquez    schedule 23.07.2015
comment
@Ghost_Stark, почему ваш метод извлечения принимает O(nlogn)?   -  person Eric Z    schedule 23.07.2015
comment
Как насчет максимальной кучи с указателем на минимальный лист?   -  person Beta    schedule 23.07.2015
comment
@Beta В любом случае вам нужно будет искать новый минимальный лист каждый раз, когда предыдущий минимальный лист удаляется.   -  person M. Shaw    schedule 23.07.2015
comment
@Ghost_Stark Разве это не должно быть O(logn) + O(n) = O(n)?   -  person Eric Z    schedule 23.07.2015
comment
@EricZ Ха, ты прав. Не знаю, о чем я думал. Позвольте мне исправить пост.   -  person David Velasquez    schedule 23.07.2015
comment
Если вы ищете чего вы хотите (а не как достичь своей текущей цели (см. xy-problem)), вы найдете двойные приоритетные очереди и интервальные кучи.   -  person greybeard    schedule 23.07.2015


Ответы (4)


Самый простой способ — просто использовать бинарное дерево поиска, как рекомендует М. Шоу.

Если вам нужно построить это поверх двоичных куч, то в каждой куче, рядом с каждым элементом, сохраните позицию элемента в другой куче. Каждый раз, когда вы перемещаете элемент в одной куче, вы можете сразу перейти к его положению в другой куче и обновить его. Когда вы выполняете delete-min или delete-max, дорогостоящее линейное сканирование в другой куче не требуется.

Например, если вы храните std::pairs с first в качестве значения элемента и second в качестве позиции в другой куче, замена двух элементов в минимальной куче при обновлении их аналогов в максимальной куче может выглядеть следующим образом:

swap(minheap[i], minheap[j]);
maxheap[minheap[i].second].second = i;
maxheap[minheap[j].second].second = j;
person user2357112 supports Monica    schedule 23.07.2015
comment
Это будет работать, но я не знаком с этой возможностью pair. Было бы проще просто использовать структуру, которая имеет minHeapIndex, maxHeapIndex и value член для каждого элемента? - person David Velasquez; 23.07.2015
comment
@Ghost_Stark: Я не знаю, будет ли это проще, но вы, безусловно, можете это сделать. - person user2357112 supports Monica; 23.07.2015
comment
@user2357112 user2357112 alongside each element, store the element's position in the other heap -- Небольшая проблема. Когда создается первая куча, как сохранить в the other heap, когда второй кучи даже не существует? Требуется самостоятельная структура. Например, хеш-таблица, индексированная по ключу элемента. Хешированное значение может быть структурой, состоящей из позиций в двух кучах. - person Eric Z; 23.07.2015
comment
@EricZ: я не уверен, что ты пытаешься сказать. Почему не существует другой кучи? Вы должны создать обе кучи, прежде чем пытаться что-либо вставить. Кроме того, группировка идет store the element's (position in the other heap), а не (store the element's position) in the other heap, если это неясно. - person user2357112 supports Monica; 23.07.2015
comment
@user2357112. Например, вы сначала создаете minHeap, а затем maxHeap. Когда вы создаете minHeap, например, для элемента A с индексом массива 3, вам нужно сохранить индекс 3 в элементе A' в maxHeap, верно? Но в то время maxHeap не было. - person Eric Z; 23.07.2015
comment
@EricZ: Вы предполагаете, что мы заполним всю кучу, прежде чем инициализировать другую? Как я себе это представляю, мы инициализируем две пустые кучи и вместе выполняем вставки в них. - person user2357112 supports Monica; 23.07.2015
comment
@ user2357112 Я именно так и делаю. И даже если бы я хотел заполнить всю кучу заданным набором данных, я все равно мог бы выполнять вставку по одному линейным способом, чтобы заполнить обе кучи одновременно. - person David Velasquez; 23.07.2015
comment
@user2357112 user2357112 Да, эта инкрементная сборка должна работать. - person Eric Z; 23.07.2015

Вы можете создать хеш-таблицу для элементов кучи, которая используется двумя кучами. Таблица индексируется по значению элемента кучи. Значение хешированного сегмента может быть структурой, состоящей из индекса массива в minHeap и maxHeap соответственно.

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

E.g.,

struct tIndex
{
   // Array index of the element in two heaps respectively
   size_t minIndex;
   size_t maxIndex;
};

std::unordered_map<int, tIndex> m;

введите здесь описание изображения

Обратите внимание, что любое изменение кучи может привести к изменению базового индекса массива существующих элементов. Поэтому, когда вы добавляете/удаляете элемент или меняете местами два элемента, вам может потребоваться соответствующим образом обновить его индекс массива в хеш-таблице.

person Eric Z    schedule 23.07.2015
comment
Мне это нравится. Я тоже ценю фотографии, очень полезные. Но я думаю, что буду придерживаться приведенного выше ответа для простоты. - person David Velasquez; 23.07.2015

Вы близко. Хитрость заключается в использовании другого уровня косвенности. Храните ключи в массиве K[i] и храните только индексы i в кучах. Также сохраните две обратные карты: одну для максимальной кучи и одну для минимальной. Обратная карта — это массив целых чисел R, таких что R[i] — это расположение в минимальной (или максимальной) куче индекса i для ключа K[i]. Другими словами, если M[j] — минимальная (или максимальная) куча, то R[M[j]] = j; Теперь всякий раз, когда вы выполняете операцию просеивания для перемещения элементов в куче, вы должны одновременно обновлять соответствующую обратную карту. На самом деле это работает точно так же, как соотношение выше. На каждом шаге, когда вы меняете элемент кучи M[j] = z, также обновляйте обратную карту R[z] = j; Это увеличивает время работы лишь на небольшой постоянный коэффициент. Теперь, чтобы удалить K[i] из кучи, вы можете найти его за постоянное время: оно находится в M[R[i]]. Просейте его до корня и удалите.

Я знаю, что это работает (поиск объекта кучи для удаления за постоянное время), потому что я реализовал его как часть более крупного алгоритма. Посетите https://github.com/gene-ressler/lulu/blob/master/ext/lulu/pq.c . Более крупный алгоритм предназначен для слияния маркеров карты: https://github.com/gene-ressler/lulu/wiki

person Gene    schedule 23.07.2015
comment
Это немного сбивает меня с толку, возможно, из-за всех букв K, R, M, i, j и z. Мне нужно подумать об этом через ха. Это также кажется очень интенсивным по памяти. Две кучи, один массив для хранения индексов и две обратные карты. Не уверен, что это то, чего хотел бы мой профессор. - person David Velasquez; 23.07.2015