Удалить элемент из середины std :: heap - PullRequest
12 голосов
/ 19 января 2011

Я использую очередь с приоритетами в качестве планировщика с одним дополнительным требованием. Мне нужно иметь возможность отменить запланированные пункты. Это равносильно удалению элемента из середины очереди с приоритетами.

Я не могу использовать std::priority_queue, так как доступ к любому элементу, кроме top, защищен.

Я пытаюсь использовать функции кучи algorithm. Но я все еще скучаю по той части, которая мне нужна. Когда я удаляю элемент I из середины кучи, я хочу, чтобы он эффективно восстанавливал себя. C ++ предоставляет следующие функции кучи:

  • std::make_heap O (3n)
  • std::push_heap O (lg (n))
  • std::pop_heap O (2 lg (n))

Я хочу новую функцию, такую ​​как std::repair_heap с большим O <<em> 3n . Я бы предоставил ему местоположение отверстия, в котором находился отмененный элемент, и он правильно настроил кучу.

Кажется огромным упущением не предоставлять функцию std::repair_heap. Я что-то упускаю из виду?

Есть ли библиотека, предоставляющая stl-совместимый std::repair_heap?

Есть ли лучшая структура данных для моделирования планировщика?

Примечание
Я не использую std::map по нескольким причинам.

  • Куча имеет постоянные накладные расходы памяти.
  • У кучи отличная локализация кеша.

Ответы [ 5 ]

6 голосов
/ 09 августа 2011

Полагаю, вы знаете, какой элемент в контейнере кучи (индекс n) вы хотите удалить.

  1. Установите значение v[n] = BIG; значение BIG действительно больше, чем любые другие значения в куче.
  2. Звоните std::push_heap( v.begin(), v.begin()+n+1 );
  3. Звоните std::pop_heap( v.begin(), v.end() );
  4. Звоните v.pop_back();
  5. Выполнено

Операция O (ln (n))

RE: запрос на доказательство

Во-первых, классификатор: Этот метод предполагает что-то в алгоритме, используемом std push_heap.
В частности, предполагается, что std push_heap (v.begin (), v.begin () + n + 1) только изменит диапазон [0, n]
для тех элементов, которые являются потомками n, то есть индексы в следующем наборе:

A(n)={n,(n-1)/2,((n-1)/2-1)/2....0}.  

Вот типичная спецификация для std push_heap:

http://www.cplusplus.com/reference/algorithm/push_heap/ «Учитывая диапазон кучи [first, last-1), эта функция расширяет диапазон, который считается кучей, до [first, last), помещая значение в (last-1) в соответствующее ему место в нем.»

Гарантирует ли это использование «нормального алгоритма кучи», о котором вы читали в учебниках? Вы говорите мне.

В любом случае, вот код, который вы можете запустить и увидеть, эмпирически, что он работает. Я использую VC 2005.

#include <algorithm>
#include <vector>
#include <iostream>

bool is_heap_valid(const std::vector<int> &vin)
{
    std::vector<int> v = vin;
    std::make_heap(v.begin(), v.end());
    return std::equal(vin.begin(), vin.end(), v.begin());
}


int _tmain(int argc, _TCHAR* argv[])
{
    srand(0);
    std::vector<int> v;
    for (int i=0; i<100; i++)
    {
        v.push_back( rand() % 0x7fff );
    }
    std::make_heap(v.begin(), v.end());

    bool bfail = false;
    while( v.size() >= 2)
    {
        int n = v.size()/2;
        v[n] = 0x7fffffff;
        std::push_heap(v.begin(), v.begin()+n+1);
        std::pop_heap(v.begin(), v.end());
        v.resize(v.size()-1);
        if (!is_heap_valid(v))
        {
            std::cout << "heap is not valid" << std::endl;
            bfail = true;
            break;
        }
    }
    if (!bfail)
        std::cout << "success" << std::endl;

    return 0;

}

Но у меня есть еще одна проблема: как узнать индекс "n", который нужно удалить. Я не вижу, как отслеживать это (знать место в куче) при использовании std push_heap и std pop_heap. Я думаю, вам нужно написать свой собственный код кучи и записать индекс в куче для объекта каждый раз, когда объект перемещается в куче. Вздох.

4 голосов
/ 19 января 2011

К сожалению, в стандарте отсутствует эта (довольно важная) функция.С g ++ вы можете использовать нестандартную функцию std::__adjust_heap, чтобы сделать это, но нет простого переносимого способа сделать это - и __adjust_heap немного отличается в разных версиях g ++, поэтому вы даже не можете сделать этопереносимо на версии g ++.

3 голосов
/ 19 января 2011

Как работает repair_heap()?Вот мое предположение:

Если ваша куча определена некоторым диапазоном итераторов, скажем (heapBegin, heapEnd).Элемент, который вы хотите удалить, является корнем некоторого поддерева кучи, которое определяется некоторым поддиапазоном (subHeapBegin, subHeapEnd).Используйте std::pop_heap(subHeapBegin, subHeapEnd), затем, если subHeapEnd != heapEnd, поменяйте местами значения *(subHeapEnd-1) и *(heapEnd-1), что должно поместить удаленный элемент в конец контейнера кучи.Теперь вам нужно просочить элемент в * (subHeapEnd-1) вверх в вашем подпапке.Если я не пропустил что-то, что возможно, то все, что остается, - это отрубить конечный элемент из контейнера кучи.

Перед тем, как попытаться правильно написать код (я пропустилнекоторые детали, такие как вычисление subHeapBegin и subHeapEnd), я бы запустил несколько тестов, чтобы определить, действительно ли make_heap() замедляет вас.Big-O полезен, но это не то же самое, что фактическое время выполнения.

0 голосов
/ 27 января 2011

Вот немного кода Delphi, который я использовал для удаления элементов из кучи. Я не знаю этот C ++, о котором вы говорите, и у вас нет функции восстановления, но эй ..

Сначала поп, так что вы получите представление о том, как это работает:

function THeap.Pop: HeapItem;
begin
  if fNextIndex > 1 then begin
    Dec(fNextIndex);
    Result:= fBuckets[1];   //no zero element
    fBuckets[1] := fBuckets[fNextIndex];
    fBuckets[fNextIndex] := nil;
    FixHeapDown;            //this has a param defaulting to 
    end
  else
    Result:= nil;
end;

Теперь для контраста, удаление:

procedure THeap.Delete(Item: HeapItem);
var
  i:integer;
begin
  for i:=1 to pred(fNextIndex) do
    if Item=fBuckets[i] then begin
      dec(fNextIndex);
      fBuckets[i] := fBuckets[fNextIndex];
      fBuckets[fNextIndex] := nil;
      FixHeapDown(i);
      break;
      end;
end;

это, конечно, нет-нет, даже думать о делать то, что мы делаем здесь, но эй, стоит меняются иногда и рабочие места отменяются.

наслаждаться. Надеюсь, это поможет.

0 голосов
/ 19 января 2011

Мне кажется, что удаление из середины кучи может означать, что вся куча должна быть восстановлена: причина того, что нет repair_heap, заключается в том, что она должна была бы выполнять ту же (big-oh) работу, что и make_heap.

Можете ли вы сделать что-то вроде добавления std::pair<bool, Item> в кучу и просто сделать недействительными элементы вместо их удаления? Затем, когда они наконец доберутся до вершины, просто проигнорируйте предмет и двигайтесь вперед.

...