Получение элементов `std :: priority_queue` в обратном порядке? - PullRequest
6 голосов
/ 25 февраля 2012

Я написал несколько методов запроса K-ближайших соседей, которые строят список точек, ближайших к данной точке запроса.Чтобы поддерживать этот список соседей, я использую std::priority_queue так, чтобы верхний элемент был самым дальним соседом к точке запроса.Таким образом, я знаю, должен ли я нажать новый элемент, который в данный момент проверяется (если он находится на меньшем расстоянии, чем текущий самый дальний сосед), и может выдавать () самый дальний элемент, когда моя очередь приоритетов имеет более K элементов.

Пока все хорошо.Однако при выводе элементов я бы хотел упорядочить их от ближайшего к самому дальнему.В настоящее время я просто извлекаю все элементы из очереди приоритетов и помещаю их в выходной контейнер (через итератор), что приводит к последовательности точек, упорядоченных от самого дальнего к ближайшему, поэтому затем я вызываю std::reverse длядиапазон итератора вывода.

В качестве простого примера приведем линейный поиск, использующий очередь приоритетов (очевидно, используемые мной методы запросов к ближайшим соседям гораздо сложнее):

  template <typename DistanceValue,
            typename ForwardIterator,
            typename OutputIterator,
            typename GetDistanceFunction,
            typename CompareFunction>
  inline 
  OutputIterator min_dist_linear_search(ForwardIterator first,
                                        ForwardIterator last,
                                        OutputIterator output_first,
                                        GetDistanceFunction distance,
                                        CompareFunction compare,
                                        std::size_t max_neighbors = 1,
                                        DistanceValue radius = std::numeric_limits<DistanceValue>::infinity()) {
    if(first == last) 
      return output_first;

    typedef std::priority_queue< std::pair<DistanceValue, ForwardIterator>, 
                                 std::vector< std::pair<DistanceValue, ForwardIterator> >,
                                 detail::compare_pair_first<DistanceValue, ForwardIterator, CompareFunction> > PriorityQueue; 

    PriorityQueue output_queue = PriorityQueue(detail::compare_pair_first<DistanceValue, ForwardIterator, CompareFunction>(compare));

    for(; first != last; ++first) {
      DistanceValue d = distance(*first);
      if(!compare(d, radius)) 
        continue;

      output_queue.push(std::pair<DistanceValue, ForwardIterator>(d, first));

      while(output_queue.size() > max_neighbors)
        output_queue.pop();

      if(output_queue.size() == max_neighbors)
        radius = output_queue.top().first;
    };

    OutputIterator it = output_first;
    while( !output_queue.empty() ) {
      *it = *(output_queue.top().second);
      output_queue.pop(); ++it;
    };
    std::reverse(output_first, it);
    return it;
  };

Все вышеперечисленное - просто отличная вещь, за исключением одного: требуется, чтобы тип выходного итератора был двунаправленным и, по сути, указывал на предварительно выделенный контейнер.Теперь эта практика хранения выходных данных в диапазоне, заданном каким-либо итератором вывода, также великолепна и довольно стандартна (например, std::copy и другие алгоритмы STL являются хорошими примерами этого).Однако в этом случае я хотел бы иметь возможность требовать только тип прямого итератора вывода, который позволил бы использовать итераторы обратного вставки, подобные тем, которые предусмотрены для контейнеров STL и iostreams.

Итак, все сводится к обращению очереди приоритетов перед тем, как выгрузит содержимое в итератор вывода.Итак, вот лучшие варианты, которые я смог придумать:

  • Создайте std::vector, поместите в него содержимое очереди приоритетов и сбросьте элементы вИтератор вывода с использованием обратного итератора для вектора.

  • Замените std::priority_queue отсортированным контейнером (например, std::multimap), а затем выгрузите содержимое в итератор вывода.используя соответствующий порядок обхода.

Есть ли другой разумный вариант?

Я использовал std::multimap в предыдущей реализации этого алгоритма и других, так какмоего второго варианта выше.Однако когда я переключился на std::priority_queue, прирост производительности был значительным.Поэтому я бы предпочел не использовать второй вариант, так как действительно кажется, что использование очереди приоритетов для ведения списка соседей намного лучше, чем использование отсортированного массива.Кстати, я также попробовал std::vector, который я поддерживаю отсортированным с std::inplace_merge, который был лучше, чем multimap, но не соответствовал приоритетной очереди.

Что касается первого варианта, которыймой лучший вариант на данный момент, мне просто кажется расточительным делать эту двойную передачу данных (очередь -> вектор -> вывод).Я просто склонен думать, что должен быть более простой способ сделать это ... что-то, чего мне не хватает ..

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

Ответы [ 3 ]

5 голосов
/ 25 февраля 2012

Проблема решена!

Я такой идиот ... Я знал, что упускаю что-то очевидное.В этом случае функция std::sort_heap().На справочной странице даже есть пример, который делает именно то, что мне нужно (а поскольку std::priority_queue просто реализован в терминах контейнера произвольного доступа и функций кучи (pop_heap, push_heap, make_heap)не имеет особого значения использовать эти функции непосредственно вместо класса std::priority_queue).Я не знаю, как я мог пропустить это.

В любом случае, я надеюсь, что это поможет любому, у кого была такая же проблема.

3 голосов
/ 25 сентября 2012

почему бы вам просто не указать противоположную функцию сравнения в объявлении:

#include <iostream>
#include <queue>
#include <vector>
#include <functional>

int main() {
    std::priority_queue<int, std::vector<int>, std::greater<int> > pq;
    pq.push(1);
    pq.push(10);
    pq.push(15);
    std::cout << pq.top() << std::endl;
}
3 голосов
/ 25 февраля 2012

Одна грязная идея, которая, тем не менее, гарантированно сработает, будет следующей:

std::priority_queue<int, std::vector<int>, std::less<int> > queue;
queue.push(3);
queue.push(5);
queue.push(9);
queue.push(2);

// Prints in reverse order.
int* front = const_cast<int*>(&queue.top());
int* back = const_cast<int*>(front + queue.size());
std::sort(front, back);
while (front < back) {
    printf("%i ", *front);
    ++front;
}

Можно отметить, что сортировка на месте, скорее всего, сломает очередь.

...