приоритетная очередь с ограниченным пространством: поиск хорошего алгоритма

Это не домашняя работа.

Я использую малочисленную "приоритетную очередь" (реализованный как массив в данный момент) для хранения последних объектов N с самым маленьким значением. Это немного медленно - O (N) время вставки объекта. Текущая реализация отслеживает самый большой объект в массиве и отбрасывает любые объекты, которые не вписались бы в массив, но я все еще хотел бы сократить количество операций далее.

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

  1. очередь может быть реализована как массив, который имеет фиксированный размер, и _cannot_ растут. Динамическое выделение памяти во время любой работы с очередями строго запрещается.
  2. Что-либо, что не вписывается в массив, отбрасывается, но очередь сохраняет все самые маленькие элементы когда-либо встреченными.
  3. O (журнал (N)) время вставки (т.е. добавляющий элемент в очередь должен взять до O (журнал (N))).
  4. (Дополнительно) O (1) доступ для *самый большой* объект в очереди (хранилища очереди *самый маленький* объекты, таким образом, самый большой объект будет отброшен сначала и мне будут нужны они для сокращения количества операций),
  5. Легкий реализовать/понять. Идеально - что-то подобное двоичному поиску - после того как Вы понимаете это, Вы помните это навсегда.
  6. Элементы не должны быть отсортированы всегда. Я просто должен сохранять самое маленькое значение N когда-либо встреченным. Когда мне будут нужны они, я получу доступ ко всем ним сразу. Так технически это не должна быть очередь, мне просто нужен N в последний раз самые маленькие значения, которые будут сохранены.

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

Так, какие-либо другие идеи?

Править--
Я не интересуюсь реализацией STL. Реализация STL (предложенный несколькими людьми) работает немного медленнее, чем в настоящее время используемая линейная матрица из-за высокого количества вызовов функции.

Я интересуюсь приоритетными алгоритмами очереди, не implemnetations.

11
задан SigTerm 29 May 2010 в 07:37
поделиться

7 ответов

Кучи на основе массивов кажутся идеальными для вашей цели. Я не уверен, почему вы отвергли их.

Вы используете максимальную кучу.

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

Когда приходит элемент, вы проверяете его на соответствие max (время O(1)), и отбрасываете, если он больше.

Если поступающее значение меньше, вы изменяете корень на новое значение и отсеиваете вниз это измененное значение - в худшем случае время O(log N).

Процесс отсеивания прост: Начиная с корня, на каждом шаге вы меняете это значение с его большим дочерним значением, пока свойство max-heap не будет восстановлено.

Таким образом, вам не придется делать никаких удалений, которые, вероятно, придется делать, если вы используете std::priority_queue. В зависимости от реализации std::priority_queue, это может привести к выделению/деаллокации памяти.

Итак, вы можете иметь следующий код:

  • Выделенный массив размера N.
  • Заполните его первыми N элементами, которые вы видите.
  • heapify (вы должны найти это в стандартных учебниках, там используется sift-down). Это O(N).
  • Теперь любой новый элемент, который вы получаете, вы либо отвергаете за время O(1), либо вставляете путем отсеивания вниз за время O(logN) в худшем случае.

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

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

Посмотрите на странице вики, где есть псевдокод для heapify и sift-down: http://en.wikipedia.org/wiki/Heapsort

14
ответ дан 3 December 2019 в 04:51
поделиться

Используйте std :: priority_queue с наибольшим элементом во главе. Для каждого нового элемента отбрасывайте его, если он > = элемент заголовка, в противном случае вытяните элемент заголовка и вставьте новый элемент.

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

8
ответ дан 3 December 2019 в 04:51
поделиться

Большинство приоритетных очередей, с которыми я работаю, основаны на связанных списках. Если у вас есть заранее определенное количество уровней приоритета, вы можете легко создать очередь приоритетов с вставкой O (1), имея массив связанных списков - один связанный список на уровень приоритета. Элементы с одинаковым приоритетом, конечно, будут преобразованы в FIFO, но это можно считать приемлемым.

Добавление и удаление затем становится чем-то вроде (ваш API может отличаться) ...

listItemAdd (&list[priLevel], &item);      /* Add to tail */
pItem = listItemRemove (&list[priLevel]);  /* Remove from head */

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

  1. В структуре очереди с приоритетами сохраните указатель или индекс или что-то еще в связанном списке с текущим наивысшим приоритетом. Это нужно будет обновлять каждый раз, когда элемент добавляется или удаляется из очереди приоритетов.
  2. Используйте растровое изображение, чтобы указать, какие связанные списки не пусты. В сочетании с алгоритмом поиска наиболее значимого бита или алгоритма поиска наименее значимого бита обычно можно протестировать до 32 списков одновременно. Опять же, это нужно будет обновлять при каждом добавлении / удалении.

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

1
ответ дан 3 December 2019 в 04:51
поделиться

Если количество приоритетов невелико и фиксировано, вы можете использовать кольцевой буфер для каждого приоритета. Это приведет к потере пространства, если объекты большие, но если их размер сопоставим с указателем / индексом, то варианты с сохранением дополнительных указателей в объектах могут таким же образом увеличить размер массива.
Или вы можете использовать простой односвязный список внутри массива и хранить 2 * M + 1 указателя / индекса, один будет указывать на первый свободный узел, а другие пары будут указывать на начало и конец каждого приоритета. В этом случае вам придется сравнивать в среднем. O (M) перед удалением следующего узла с помощью O (1). И вставка займет O (1).

0
ответ дан 3 December 2019 в 04:51
поделиться

Если вы создадите приоритетную очередь STL с максимальным размером (возможно, из вектора, инициализированного заполнителями), а затем проверите размер перед вставкой (удалив элемент, если необходимо, заранее), у вас никогда не будет динамического распределения во время операций вставки. Реализация STL довольно эффективна.

0
ответ дан 3 December 2019 в 04:51
поделиться

Нашел решение («разница» означает «приоритет» в коде, а maxRememberedResults - 255 (может быть любым (2 ^ n - 1)):

template <typename T> inline void swap(T& a, T& b){
    T c = a;
    a = b;
    b = c;
}


struct MinDifferenceArray{
    enum{maxSize = maxRememberedResults};
    int size;
    DifferenceData data[maxSize];
    void add(const DifferenceData& val){
        if (size >= maxSize){
            if(data[0].difference <= val.difference)
                return;

            data[0] = val;

            for (int i = 0; (2*i+1) < maxSize; ){
                int next = 2*i + 1;
                if (data[next].difference < data[next+1].difference)
                    next++;
                if (data[i].difference < data[next].difference)
                    swap(data[i], data[next]);
                else
                    break;
                i = next;
            }
        }
        else{
            data[size++] = val;
            for (int i = size - 1; i > 0;){
                int parent = (i-1)/2;
                if (data[parent].difference < data[i].difference){
                    swap(data[parent], data[i]);
                    i = parent;
                }
                else
                    break;
            }
        }
    }

    void clear(){
        size = 0;
    }

    MinDifferenceArray()
        :size(0){
    }
};
  1. построить очередь на основе максимального числа (корень самый большой)
  2. пока он не заполнится, заполните как обычно
  3. когда он заполнен, для каждого нового элемента
    1. Проверить, не меньше ли новый элемент корня.
    2. , если он больше или равен корню, отклонить.
    3. в противном случае замените root на новый элемент и выполните обычное «просеивание» кучи.

И мы получаем O (log (N)) insert как наихудший сценарий.

Это то же самое решение, которое предоставил пользователь с ником "Moron". Спасибо всем за ответы.

P.S. Очевидно, программирование без сна - не лучшая идея.

0
ответ дан 3 December 2019 в 04:51
поделиться

Matters Computational см. Стр. 158. Сама реализация довольно хороша, и вы даже можете немного подправить ее, не делая ее менее читаемой. Например, когда вы вычисляете левый дочерний элемент, например:

int left = i / 2;

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

int right = left + 1;
0
ответ дан 3 December 2019 в 04:51
поделиться
Другие вопросы по тегам:

Похожие вопросы: