ACM - Псков

Информация о пользователе

Привет, Гость! Войдите или зарегистрируйтесь.


Вы здесь » ACM - Псков » Структуры данных » Priority Queue (очередь с приоритетом)


Priority Queue (очередь с приоритетом)

Сообщений 1 страница 11 из 11

1

Приоритетная очередь.

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

1) Object min();
Возвращает значение элемента с минимальным ключом.

2) void insert(Comparable key, Object value);
Вставляет в очередь пару (key - value). Обратите внимание, что ключ обязательно должен поддерживать сравнение, что, в принципе, логично.

3) void removeMin();
Удаляет элемент с минимальным ключом.

Наиболее оптимальной реализацией приоритетной очереди считается двоичная куча (binary heap).

Основные свойства BinaryHeap:
1) Это полное бинарное дерево (то есть бинарное дерево, в котором заполнены все уровни, кроме, возможно, последнего)
2) Это дерево обладает свойством кучи, то есть ни один потомок не может быть меньше своего предка (стало быть, корнем всегда является элемент с наименьшим ключом).

Свойство полноты позволяет нам представить BinaryHeap в виде массива. Пример:

индекс 0 1 2 3 4 5 6 7 8   9  10 11 12 13 14
ключ    - 2 5 4 6 7 8 9 17 10 12  -   -   -   -

Здесь элемент с индексом 1 - корень. Детьми i'го элемента являются элементы 2*i и 2*i+1. Родителем i'го элемента является элемент с индексом i/2 (корень не имеет родителя).

Рассмотрим реализацию операций с кучей.

1) Object min();

Очень просто - вернуть элемент с индексом 1, то есть корень. Корень всегда имеет минимальный ключ.

2) void insert(Comparable key, Object value);

Чуть сложнее. Сначала вставляем новую запись на первое свободное место (первое пустое место в массиве, не считая 0). Далее запускаем цикл:

Х = индекс вставленного элемента
Пока ((Х имеет родителя) И (родитель имеет меньший ключ нежели Х)) обменять местами элемент по индексу Х и родителя, Х/=2

Пример: вставим в исходную очередь (см. выше) элемент с ключом 3.

Шаг 1:

индекс 0 1 2 3 4 5 6 7  8  9  10 11 12 13 14
ключ    - 2 5 4 6 7 8 9 17 10 12  3   -  -   -

Шаг 2:

индекс 0 1 2 3 4 5 6 7  8   9  10 11 12 13 14
ключ    - 2 5 4 6 3 8 9 17 10  12  7  -   -   -

Шаг 3:

индекс 0 1 2 3 4 5 6 7  8   9 10 11 12 13 14
ключ    - 2 3 4 6 5 8 9 17 10 12 7   -   -   -

Все, новый элемент вставлен.

3) void removeMin();

Удаляем корень дерева. Ставим на его место последний лист дерева. Запускаем цикл:
Х = 1
Пока ((Х имеет хотя бы одного потомка) И (хотя бы один из потомков имеет ключ < ключа элемента Х)) поменять местами элемент по индексу Х и его ребенка с МЕНЬШИМ ключом, Х = индекс этого ребенка

Пример: удалим из исходной очереди элемент с наименьшем ключом.

Шаг 1:

индекс 0 1 2 3 4 5 6 7  8   9 10 11 12 13 14
ключ    - - 5 4 6 7 8 9 17 10 12  -   -   -   -

Шаг 2:

индекс 0   1 2 3 4 5 6 7   8 9 10 11 12 13 14
ключ    - 12 5 4 6 7 8 9 17 10  -   -   -  -   -

Шаг 3:

индекс 0 1 2   3 4 5 6 7   8  9 10 11 12 13 14
ключ    - 4 5 12 6 7 8 9 17 10   -  -   -   -   -

Шаг 4:

индекс 0 1 2 3 4 5   6 7   8 9 10 11 12 13 14
ключ    - 4 5 8 6 7 12 9 17 10  -  -   -   -   -

Все, элемент удален.

Оценки алгоритмов (в тета-нотации, среднее время):
min() - O(1)
insert - O(log n)
removeMin - O(log n)

Все выше написанное является вольной интерпретацией Кнута и этой замечательной лекции:
http://www.youtube.com/watch?v=yIUFT6AK … p;index=23
(ЭТО ПЕАР!)

Моя реализация:

Код:
class PQueue{
    private static final int MAX = 1100000;

    private class Entry{
        Comparable key;
        Object value;
        Entry() {}
        Entry(Comparable _k, Object _val){
            key=_k;
            value=_val;
        }
    }

    private Entry[] arr = new Entry[MAX+1];
    private int sz = 0;

    public boolean empty(){
        return sz==0;
    }

    public Object min()throws Exception{
        if(sz==0)
            throw new Exception("Empty queue!");
        return arr[1].value;
    }

    public void insert(Comparable key, Object value)throws Exception{
        if(sz==524288)
            throw new Exception("Overflow!");
        arr[++sz]= new Entry(key,value);
        int ind = sz;
        while(ind>1){
            if(arr[ind].key.compareTo(arr[ind/2].key)<0){
                Entry tmp=arr[ind];
                arr[ind]=arr[ind/2];
                arr[ind/2]=tmp;
                ind/=2;
            }
            else
                break;
        }
    }

    public void removeMin() throws Exception{
        if(sz==0)
            throw new Exception ("Empty queue!");
        arr[1]=null;
        if(sz==1){
            sz=0;
            return;
        }
        arr[1]=arr[sz];
        arr[sz--]=null;
        int ind=1;
        while((arr[2*ind]!=null && arr[2*ind].key.compareTo(arr[ind].key)<0) || (arr[2*ind+1]!=null &&  arr[2*ind+1].key.compareTo(arr[ind].key)<0)){
            if(arr[2*ind]==null){
                Entry tmp=arr[ind];
                arr[ind]=arr[2*ind+1];
                arr[2*ind+1] = tmp;
                break;
            }
            if(arr[2*ind+1]==null){
                Entry tmp=arr[ind];
                arr[ind]=arr[2*ind];
                arr[2*ind] = tmp;
                break;
            }
            if(arr[2*ind].key.compareTo(arr[2*ind+1].key)<0){
                Entry tmp=arr[ind];
                arr[ind]=arr[2*ind];
                arr[2*ind] = tmp;
                ind=ind*2;
            }
            else{
                Entry tmp=arr[ind];
                arr[ind]=arr[2*ind+1];
                arr[2*ind+1] = tmp;
                ind=ind*2+1;
            }
        }
    }
}

Отредактировано Ikari (2009-01-08 16:33:24)

2

Есть несколько комментариев :):
1. Именно такую приоритетную очередь не получится использовать в алгоритме дейкстры, потому как для использования в алгоритме Дейкстры в оччереди должно быть взаимооднозначное соответсвие между елементом очереде и вершиной в графе.
2. Чем не устраивают уже существующие приоритетные очереди? В java это PriorityQueue, в STL тоже есть кажется priorityqueue. (их кстати тоже нельзя использовать в алгоритме Дейкстры)
3. Работа алгоритма Дейкстры с приоритеной очередью эффективнее чем алгоритм дейкстры с обычным массивом только если граф разреженый, т.е. количество ребер меньше чем V*V/(lgV). Правда если не использовать пирамиды Фибоначчи

А вот кстати приоритетная очередь которую можно использовать в алгоритме дейкстры (на С++):

Код:
template<class KeyType>
class IndexedPriorityQLow
{
private:
  std::vector<KeyType>&  m_vecKeys;
  std::vector<int>       m_Heap;
  std::vector<int>       m_invHeap;
  int                    m_iSize,
                         m_iMaxSize;

  void Swap(int a, int b)
  {
    int temp = m_Heap[a]; m_Heap[a] = m_Heap[b]; m_Heap[b] = temp;
    //change the handles too
    m_invHeap[m_Heap[a]] = a; m_invHeap[m_Heap[b]] = b;
  }
  void ReorderUpwards(int nd)
  {
    //move up the heap swapping the elements until the heap is ordered
    while ( (nd>1) && (m_vecKeys[m_Heap[nd/2]] > m_vecKeys[m_Heap[nd]]) )
    {      
      Swap(nd/2, nd);
      nd /= 2;
    }
  }
  void ReorderDownwards(int nd, int HeapSize)
  {
    //move down the heap from node nd swapping the elements until
    //the heap is reordered
    while (2*nd <= HeapSize)
    {
      int child = 2 * nd;
      //set child to smaller of nd's two children
      if ((child < HeapSize) && (m_vecKeys[m_Heap[child]] > m_vecKeys[m_Heap[child+1]]))
      {
        ++child;
      }
      //if this nd is larger than its child, swap
      if (m_vecKeys[m_Heap[nd]] > m_vecKeys[m_Heap[child]])
      {
        Swap(child, nd);
        //move the current node down the tree
        nd = child;
      }
      else
      {
        break;
      }
    }
  }
public:
 //you must pass the constructor a reference to the std::vector the PQ
  //will be indexing into and the maximum size of the queue.
  IndexedPriorityQLow(std::vector<KeyType>& keys,
                      int              MaxSize):m_vecKeys(keys),
                                                m_iMaxSize(MaxSize),
                                                m_iSize(0)
  {
    m_Heap.assign(MaxSize+1, 0);
    m_invHeap.assign(MaxSize+1, 0);
  }

  bool empty()const{return (m_iSize==0);}

  //to insert an item into the queue it gets added to the end of the heap
  //and then the heap is reordered from the bottom up.
  void insert(const int idx)
  {
    assert (m_iSize+1 <= m_iMaxSize);

    ++m_iSize;

    m_Heap[m_iSize] = idx;

    m_invHeap[idx] = m_iSize;

    ReorderUpwards(m_iSize);
  }

  //to get the min item the first element is exchanged with the lowest
  //in the heap and then the heap is reordered from the top down. 
  int Pop()
  {
    Swap(1, m_iSize);

    ReorderDownwards(1, m_iSize-1);

    return m_Heap[m_iSize--];
  }

  //if the value of one of the client key's changes then call this with 
  //the key's index to adjust the queue accordingly
  void ChangePriority(const int idx)
  {
    ReorderUpwards(m_invHeap[idx]);
  }
};

3

atimofeyev написал(а):

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

Это соответствие легко вшаманивается в очередь.
К тому же можно при изменении дистанции вершины просто запихать её ещё раз в очередь (правда съест больше памяти, но не намного)

4

Ktulhu написал(а):

Это соответствие легко вшаманивается в очередь.

Пример пожалуйста.

5

Я не в совершенстве представляю алгоритм, но кто мешает каждой вершине графа сопоставить ссылку на элемент очереди и при каждом изменении
менять элемент очереди и поднимать/опускать его в бинарной куче?

6

В том то и дело, что когда мы добавляем добавляем вершину в очередь мы не знаем в каком месте очереди она окажется. В зависимости от приоритета она может быть в почти любом месте. (А операция поднятия/опускания элемента очереди нуждается имено в индексе элемента в очереди а не самого объекта). Стандартные имплементации очередей такого не позволяют, у них даже нет операции опустить/поднять.
Поэтому то в приведенном примере и используется доболнительный инвертированный массив, который при опускании/поднятии элемента в очереди меняет свои значения. И дополнительный метод ChangePriority. Т.е. работая с очередью мы передаем номер вершины, в очереди по этому номеру берется ключ, по которому определяется приоритет, а так же позиция в куче, чтобы можно было поднять/опустить этот элемент.

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

Отредактировано atimofeyev (2009-01-12 11:17:54)

7

А чем так плох вариант пихать в очередь вершину при её изменении  второй раз? А потом рассматривать только актуальные элементы очереди?

8

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

9

Смысл АСМ - в шаманстве над алгоритмами и перекраивании их под свои нужды. )))

10

Совсем даже не шаманство. Стандартный приём. Да и работает всё равно быстрее, чем "правильно" написанная Дейкстра на каком-нибудь set :)

Собственноручно написанная куча, конечно, побыстрей будет, однако на контесте такое писать нереально.

11

Часто на контесте дают сильно связный граф, а там реализация дейкстры с массивом будет лучше чем эта куча.
Как я уже говорил, согласен, для контеста это хороший прием, но к такому привыкать категорически запрещается. Потому как если есть правильная реализация и при этом более эффективная, лучше использовать ее, а не все-таки шаманский стандартный прием (потому как он будет работать только с дополнительной обработкой - при доставании вершины мы должны проверить была ли она уже у нас, а это уже шаманство, конечно ниского разряда, но все же). А то потом привыкнешь шаманить, и будет твой код понятен только тебе одному. Надо быть культурным кодером вобщем :).

Отредактировано atimofeyev (2009-01-16 11:07:59)


Вы здесь » ACM - Псков » Структуры данных » Priority Queue (очередь с приоритетом)