Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2178 / 0 | Длительность: 63:16:00
Лекция 9:

Очереди с приоритетами и пирамидальная сортировка

Программа 9.5. Очередь с приоритетами на базе пирамидального дерева

При выполнении операции вставки (insert) мы увеличиваем N на 1, добавляем новый элемент в конец дерева, а затем вызываем процедуру fixUp для восстановления пирамидальности. При извлечении наибольшего элемента (getmax) мы выбираем в качестве возвращаемого значения значение pq[1], потом уменьшаем размер дерева на 1, перенеся значение pq[N] в pq[1], а затем вызываем процедуру fixDown для восстановления пирамидальности. Реализации конструктора и функции empty тривиальны. Первая позиция pq[0] массива здесь не используется и может быть задействована в других реализациях в качестве сигнального ключа.

  template <class Item>
  class PQ
    { private:
        Item *pq;
        Int N;
      public:
        PQ(int maxN)
        { pq = new Item[maxN+1]; N = 0; }
      int empty() const
        { return N == 0; }
      void insert (Item, item)
        { pq[++N] = item; fixUp(pq, N); }
      Item getmax()
        { exch(pq[1], pq[n]);
fixDown(pq, 1, N-1);
return pq[N--];
        }
    };
        

На рис. 9.5 и рис. 9.6 показан пример построения пирамидального дерева последовательными вставками элементов в первоначально пустое пирамидальное дерево. В используемом нами представлении пирамидального дерева в виде массива этот процесс соответствует пирамидальному упорядочению массива: при каждой вставке нового элемента размер дерева увеличивается на 1, а для восстановления пирамидальности используется процедура fixUp.

 Нисходящее построение пирамидального дерева

Рис. 9.5. Нисходящее построение пирамидального дерева

Эта последовательность диаграмм демонстрирует вставку ключей A S O R T I N G в первоначально пустое пирамидальное дерево. Новые элементы добавляются на нижний уровень дерева, справа налево. Каждая вставка затрагивает только узлы на пути между точкой вставки и корнем, поэтому в худшем затраты пропорциональны логарифму размера пирамидального дерева.

 Нисходящее построение пирамидального дерева (продолжение)

Рис. 9.6. Нисходящее построение пирамидального дерева (продолжение)

Здесь показана вставка ключей E X A M P L E в пирамидальное дерево, заполнение которого начато на рис. 9.5. Общая стоимость построения пирамидального дерева размера N не превосходит lg1 + lg 2 + . . . + lgN, а эта величина меньше N lgN.

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

Базовые процедуры fixUp и fixDown из программ 9.3 и 9.4 также позволяют получить прямую реализацию операций изменить приоритет и извлечь. Чтобы изменить приоритет элемента, находящегося где-то в середине пирамидального дерева, применяется процедура fixUp для перемещения вверх по дереву, если приоритет элемента увеличивается, либо процедура fixDown для перемещения вниз по дереву, если приоритет уменьшается. Полная реализация таких операций, обращающихся к конкретным элементам данных, имеет смысл, только если для каждого элемента имеется дескриптор, указывающий место этого элемента в структуре данных. Соответствующие реализации будут подробно рассмотрены в разделах 9.5—9.7.

Лемма 9.3. Операции изменить приоритет, извлечь и заменить наибольший для АТД очереди с приоритетами могут быть реализованы с помощью пирамидально упорядоченных деревьев таким образом, что для выполнения любой из этих операций в очереди из N элементов потребуется не более 2 lgN сравнений.

Поскольку эти операции требуют наличия специальных дескрипторов, отложим рассмотрение реализаций, поддерживающих эти операции, до раздела 9.6 (см. программу 9.12 и рис. 9.14). Все они выполняют движение по пирамидальному дереву вдоль одного пути — возможно, полного пути сверху донизу или снизу доверху в худшем случае. $\blacksquare$

Обратите внимание, что в этот список не включена операция объединить. Эффективное объединение двух очередей с приоритетами требует более сложной структуры данных. Такая структура данных будет подробно рассмотрена в разделе 9.7. Но для многих приложений вполне достаточен представленный здесь простой метод на базе пирамидального дерева. Он использует минимальный объем дополнительной памяти и гарантированно обеспечивает высокую эффективность выполнения операций кроме тех случаев, когда часто выполняются операции объединить с большими объемами данных.

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

На рис. 9.5 и рис. 9.6. приведен пример первого этапа (процесс создания), в котором используется реализация очереди с приоритетами на базе пирамидального дерева; на рис. 9.7 и рис. 9.8 показан второй этап (который будем называть процессом нисходящей сортировки — sortdown). Для практических целей этот метод выглядит не очень элегантно, т.к. он без особой необходимости копирует сортируемые элементы (в очередь с приоритетами). Да и выполнение N последовательных вставок — не самый эффективный способ построения пирамидального дерева, состоящего из N элементов.

Сейчас мы рассмотрим эти два положения и выведем классический алгоритм пирамидальной сортировки.

Программа 9.6. Сортировка с помощью очереди с приоритетами

Чтобы упорядочить подмассив a[1], ..., a[r] с помощью АТД очереди с приоритетами, следует все элементы поместить в очередь при помощи функции insert, а затем функцией getmax извлечь их в порядке убывания. Этот алгоритм сортировки выполняется за время, пропорциональное N lgN, но требует дополнительного объема памяти, пропорционального количеству сортируемых элементов N (для очереди с приоритетами).

  #include "PQ.cxx"
  template <class Item>
  void PQsort(Item a[], int l, int r)
    { int k;
      PQ<Item> pq(r-l+1);
      for (k = l; k <= r; k++) pq.insert(a[k]);
      for (k = r; k >= l; k--) a[k] = pq.getmax();
    }
        

Упражнения

9.21. Приведите пирамидальное дерево, которое получается после вставки ключей E A S Y Q U E S T I O N в первоначально пустое дерево.

9.22. Воспользовавшись соглашениями, принятыми в упражнении 9.1, приведите последовательность пирамидальных деревьев, полученных в результате выполнения операций

P r I о * R * * I * T * Y * * * Q U E * * * U * E

в первоначально пустом дереве.

9.23. Поскольку в операциях по восстановлению пирамидального порядка используется примитив exch, элементы загружаются и запоминаются в два раза чаще, чем это необходимо. Предложите, в стиле сортировки вставками, более эффективные реализации, которые позволяют избежать этой проблемы.

9.24. Почему в функции fixDown не используется сигнальный ключ, чтобы не выполнять проверку j < N?

9.25. Добавьте в реализацию очереди с приоритетами на базе пирамидального дерева в программе 9.5 операцию заменить наибольший. Специально рассмотрите случай, когда добавляемое значение больше всех остальных значений в очереди. Совет: элегантное решение можно получить, задействовав элемент pq[0].

9.26. Какое минимальное количество ключей требуется переместить при выполнении в пирамидальном дереве операции извлечь наибольший? Приведите пример пирамидального дерева, содержащего 15 элементов, для которого достигается этот минимум.

9.27. Какое минимальное количество ключей требуется переместить при выполнении в пирамидальном дереве трех последовательных операций извлечь наибольший? Приведите пример пирамидального дерева из 15 элементов, для которого достигается этот минимум.

 Сортировка элементов из очереди с приоритетами

Рис. 9.7. Сортировка элементов из очереди с приоритетами

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

 Сортировка элементов из очереди с приоритетами (продолжение)

Рис. 9.8. Сортировка элементов из очереди с приоритетами (продолжение)

Эта последовательность демонстрирует извлечение остальных ключей из пирамидального дерева на рис. 9.7. Даже если каждый элемент пройдет весь путь до самого нижнего уровня, общая стоимость этапа сортировки меньше, чем lgN + . . . + lg 2 + lg1, что меньше, чем N logN.

Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?