У меня есть некоторый код, который непрерывно извлекает объект с максимальным значением из кучи и обрабатывает его. Однако во время обработки максимального значения другие объекты в куче затрагиваются, и, возможно, потребуется удалить их. Грубо говоря:
vector<HeapEntry*> myHeap = vector<HeapEntry*>();
fillHeap(myHeap, someData);
make_heap(myHeap.begin(), myHeap.end());
while (!myHeap.empty())
{
HeapEntry* hp = myHeap.front();
HeapEntry* neighbor = hp->getNeighbor();
if (someCondition)
{
remove(myHeap, neighbor);
}
//more processing of hp
}
И функция удаления:
void remove(vector<HeapEntry*> myHeap, HeapEntry* toRemove)
{
for (it = myHeap.begin(); it != myHeap.end(); it++)
{
if (*it == hp)
{
myHeap.erase(it);
break;
}
}
make_heap(myHeap.begin(), myHeap.end());
}
Это работает и дает правильный вывод. Но он медлителен до чертиков: 2 минуты на обработку файла размером 40 КБ (размер кучи линейный по размеру файла). В любом случае это должно быть более эффективным.
Функция удаления в итоге вызывается примерно n раз, где n — размер кучи. Таким образом, наличие этого линейного поиска делает весь алгоритм O (n ^ 2). Я думаю, что это проблема, и я считаю, что это может работать в O (n * log (n)).
Моя цель — сделать функцию удаления за O (log (n)). Что-то вроде:
Я не совсем уверен, как это реализовать (я почти не знаком с кучей STL).
Кто-нибудь знает, как это сделать без выполнения линейного поиска?
Простой подход не удалить элементы, которые вы хотите удалить. Вместо этого вы должны поддерживать приоритетную очередь для определения следующего элемента max и std::set<HeapEntry*>
удаленного элемента. При получении элемента max вы проверяете, есть ли он в наборе удаленных элементов, и вы просто удаляете его из кучи, пробуя следующий элемент. В зависимости от количества потенциально удаленных элементов может потребоваться также удалить элемент из набора удаленных элементов при удалении его из кучи.
Вместо удаления элементов из кучи, вы просто добавляете их в набор удаленных элементов. Таким образом, элементы кучи все еще остаются логарифмическими, и у вас может быть до O (n log n) операций над множеством элементов.
Другой альтернативой будет использование очереди приоритетов на основе узлов для эффективного поиска позиции узла в куче. Например, Boost предоставляет кучу Фибоначчи как часть библиотеки графов ускорения. Вы можете отслеживать положение элемента там. Тем не менее, кучи на основе узлов имеют тенденцию работать медленнее на практических проблемах размера из-за их издержек при перестановке элементов.
Философия stl заключается в том, чтобы сначала отразить ваш алгоритм, а затем выбрать структуру данных. Вы делаете это наоборот.
Если вы планируете удалять элементы из вашей структуры данных в «случайном» порядке, вам, вероятно, лучше priority_queue
или даже связанный list
, (Однако будьте осторожны: итераторы могут быть недействительными после удаления из некоторых контейнеров stl).
Спасибо за все ваши ответы. Я решил пойти с подходом, который фактически удаляет HeapEntries, когда они больше не действительны. На самом деле я попытался добавить действительный флаг в HeapEntry, и я думаю, что это сработало бы, если бы не некоторые другие ошибки, которые я с тех пор исправил. Во всяком случае, вот как я в конечном итоге решить это.
Чтобы повторить, мне нужна была возможность удалить элемент из кучи, учитывая только указатель на этот элемент. Проблема в том, что указатель ничего не сказал мне о позиция элемента в куче. Поэтому я решил сохранить позицию, обновлять ее при каждом перемещении элементов и написать функцию для удаления из кучи с заданной позицией. Проще говоря, куча сохраняется в виде массива, а позиции элементов определяют отношения родитель / потомок. Родитель элемента должен находиться на этаже местоположения ((myPos — 1) / 2), а его дочерние элементы должны находиться в позициях 2 * myPos + 1 и 2 * myPos + 2. Я понял, что могу написать функцию удаления (позиции) и, в то же время, меняя элементы для сохранения свойства кучи, можно также поменять их сохраненные позиции. Вот ссылка на результат, и он ускорил выполнение в 5 или 10 раз: