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

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

Пирамидальная сортировка

Идею, лежащую в основе программы 9.6, можно модифицировать так, чтобы сортировка массива не требовала дополнительной памяти — организовав пирамидальное дерево внутри сортируемого массива. Другими словами, перейдя к задаче сортировки, мы не будем скрывать очередь с приоритетами, и вместо того чтобы держаться в рамках интерфейса АТД очереди с приоритетами, будем непосредственно вызывать функции fixUp и fixDown.

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

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

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

Использование программы 9.5 непосредственно в программе 9.6 соответствует проходу по массиву слева направо, с использованием функции fixUp для организации элементов, располагающихся слева от индекса просмотра, в виде пирамидально упорядоченного полного дерева. Затем, во время выполнения нисходящей сортировки, мы помещаем наибольший элемент на место, которое освобождается при сжатии пирамидального дерева. То есть процесс нисходящей сортировки подобен сортировке выбором, однако в нем используется более эффективный способ определения наибольшего элемента в несортированной части массива. Вместо построения пирамидального дерева последовательными вставками, как показано на рис. 9.5 и рис. 9.6, эффективнее создать его с помощью прохода в обратном направлении, формируя меньшие поддеревья снизу верх, как показано на рис. 9.9. При этом каждая позиция массива рассматривается как корень небольшого поддерева, и используется тот факт, что функция fixDown работает на таких поддеревьях так же хорошо, как и на большом дереве. Если оба потомка узла являются пирамидальными деревьями, то после вызова fixDown поддерево с корнем в этом узле также становится пирамидальным деревом. Проходя по дереву в обратном направлении и вызывая fixDown в каждом узле, мы по индукции устанавливаем пирамидальный порядок. Обратный проход вдоль массива лучше начать с полпути, поскольку поддеревья, состоящие из одного узла, можно пропустить.

Полная реализация классического алгоритма пирамидальной сортировки (heapsort) приведена в программе 9.7. Циклы в этой программе выполняют, на первый взгляд, совершенно разные задачи (первый строит пирамидальное дерево, а второй разрушает его в процессе нисходящей сортировки). Однако оба они основаны на одной и той же фундаментальной процедуре, которая восстанавливает дерево, в котором уже установлен пирамидальный порядок, за исключением, возможно, корня, и использует при этом представление полного дерева в виде массива. На рис. 9.10 показано содержимое массива для примера, соответствующего рис. 9.9 рис. 9.9.

Программа 9.7. Пирамидальная сортировка

Непосредственное применение функции fixDown позволяет построить классический алгоритм пирамидальной сортировки. Цикл for выполняет построение пирамидального дерева; затем цикл while меняет местами наибольший элемент с последним элементом массива и восстанавливает пирамидальную упорядоченность, продолжая этот процесс до полного исчерпания дерева. Указатель pq с адресом a[l-1] позволяет программе рассматривать переданный ей подфайл как массив, содержащий полное дерево, с первым элементом в позиции 1 (см. рис. 9.2). В некоторых средах программирования это невозможно.

  template <class Item>
  void heapsort(Item a[], int l, int r)
    { int k, N = r-1 + 1;
      Item *pq = a+l-1;
      for (k = N/2; k >= 1; k-- )
        fixDown (pq, k, N) ;
      while (N > 1)
        { exch(pq[1], pq[N]);
        fixDown (pq, 1, —N); }
    }
        

Лемма 9.4. Для восходящего построения пирамидального дерева требуется линейное время.

Это утверждение основано на том факте, что большинство обрабатываемых пирамидальных деревьев имеет небольшие размеры. Например, для создания пирамидального дерева из 127 элементов нужно построить 32 пирамидальных дерева размером 3, 16 деревьев размером 7, 8 деревьев размером 15, 4 деревьев размером 31, два пирамидальных дерева размером 63 и одно дерево размером 127. Поэтому в худшем случае требуется $32\cdot1 + 16\cdot2 + 8\cdot3 + 4\cdot4 + 2\cdot5 + 1\cdot6 = 120$ повышений (и в два раза больше сравнений). Для N = 2N — 1 верхняя граница количества повышений равна $$\sum \imits_{1\eq k\eq n}k2^{n-k-1}=2^{n}-n-1<N$$

Аналогичное доказательство можно выполнить и для случая, когда N + 1 не является степенью 2. $\blacksquare$

Эта лемма не имеет особого значения для пирамидальной сортировки, поскольку основная доля времени ее выполнения приходится на Nlog N — время, затрачиваемое на выполнение нисходящей сортировки. Однако она играет важную роль для других применений очередей с приоритетами, в которых линейная по времени операция создать приводит к алгоритму с линейным временем выполнения. Как сказано в подписи к рис. 9.6, построение пирамидального дерева с помощью N последовательных операций вставить требует в совокупности N log N шагов в худшем случае (хотя для случайно упорядоченных файлов это количество оказывается в среднем линейным).

 Пример выполнения пирамидальной сортировки

Рис. 9.10. Пример выполнения пирамидальной сортировки

Пирамидальная сортировка представляет собой эффективный алгоритм, основанный на выборе элементов. Сначала непосредственно в сортируемом массиве восходящим способом создается пирамидальное дерево. Верхние 8 строк на этом рисунке соответствуют рис. 9.9. Затем из построенного дерева многократно извлекается наибольший элемент. Незаштрихованные части нижних строк соответствуют рис. 9.7 и рис. 9.8, а заштрихованные части содержат растущий упорядоченный файл.

Лемма 9.5. Пирамидальная сортировка использует менее 2N lgN сравнений для сортировки N элементов.

Чуть более высокая граница 3 N lg N непосредственно следует из леммы 9.2. Приведенная здесь граница следует из более точного расчета на основе леммы 9.4. $\blacksquare$

Лемма 9.5 и упорядочение на месте — вот два основных фактора, которые обусловливают интерес к пирамидальной сортировке: они гарантируют, что сортировка N элементов будет выполнена на месте и за время, пропорциональное Nlog N, независимо от природы входных данных. Для пирамидальной сортировки не бывает худших входных данных, которые существенно замедляют выполнение (в отличие от быстрой сортировки), и она не требует дополнительной памяти (в отличие от сортировки слиянием). Гарантированная эффективность для худшего случая дается не даром: например, во внутреннем цикле алгоритма (стоимость одного сравнения) содержится больше базовых операций, чем во внутреннем цикле быстрой сортировки, и для случайно упорядоченных файлов данный алгоритм выполняет больше сравнений, чем быстрая сортировка. Поэтому на обычных или случайно упорядоченных файлах пирамидальная сортировка чаще всего работает медленнее быстрой сортировки.

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

Лемма 9.6. Выборка на базе пирамидальной сортировки позволяет отыскать к-й наибольший из N элементов за время, пропорциональное N, если к мало или сравнимо с N, либо за время, пропорциональное N log N, во всех других случаях.

Один способ заключается в построении пирамидального дерева с помощью менее чем 2N сравнений (по лемме 9.4) с последующим извлечением к наибольших элементов, используя 2 к lg N или меньше сравнений (см. лемму 9.2) — итого 2 N + 2 к lg N сравнений. Другой способ связан с построением пирамидального дерева для наименьших элементов размером к и выполнением для остальных элементов операции заменить наименьший (replace the minimum) (операции вставить и извлечь наименьший), что дает в общем не более чем 2 к + 2 (N— к) lg к сравнений (см. упражнение 9.35). Этот метод использует объем дополнительной памяти, пропорциональный к, и особенно удобен для нахождения к наибольших из N элементов при малых к и больших (или не известных заранее) N. Если к мало по сравнению с N, то для случайно упорядоченных ключей и в других типичных ситуациях верхняя граница lg к для операций на пирамидальном дереве по второму методу обычно равна О(1) (см. упражнение 9.36). $\blacksquare$

Исследованы различные способы дальнейшего улучшения пирамидальной сортировки. Одна из идей, предложенная Флойдом (Floyd), состоит в следующем: элемент, повторно вставляемый в процессе нисходящей сортировки, обычно проходит весь путь до нижнего уровня — поэтому можно сэкономить время, устранив проверку достижения этим элементом своей позиции, просто обменивая его с большим из двух потомков до достижения нижнего уровня, а затем возвращаясь вверх по дереву до нужной позиции. Благодаря этой идее количество сравнений сокращается асимптотически в 2 раза и приближается к $lg N! \approx N lg N — N/ ln 2$, что является абсолютным минимумом для любого алгоритма сортировки (см. часть 8). Этот метод требует дополнительных вычислений и может быть практически полезным только при сравнительно больших затратах на операции сравнения (например, при сортировке записей со строковыми ключами или с другими видами длинных ключей).

Другая идея заключается в построении пирамидальных деревьев, основанных на представлении в виде массивов полных пирамидально упорядоченных тернарных деревьев, в которых узел в позиции к больше или равен узлам в позициях 3к — 1, 3к и 3к + 1 и меньше или равен узлу в позиции $\lfloor{(k+1)/3}\rfloor$ для всех позиций от 1 до N в массиве из N элементов. Снижение затрат за счет меньшей высоты дерева уравновешивается более высокой стоимостью выбора наибольшего из трех потомков для каждого узла. Выигрыш в данном случае зависит от деталей реализации (см. упражнение 9.30). Дальнейшее увеличение количества дочерних узлов для каждого узла не дает никаких улучшений.

На рис. 9.11 показан процесс пирамидальной сортировки произвольно упорядоченного файла. Вначале, пока при построении пирамидального дерева большие элементы перемещаются в начало файла, кажется, что этот процесс делает все что угодно, только не сортировку.

Однако далее, как положено, метод становится все больше похож на зеркальное отражение сортировки выбором.

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

 Динамические характеристики пирамидальной сортировки

Рис. 9.11. Динамические характеристики пирамидальной сортировки

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

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

 Динамические характеристики пирамидальной сортировки для различных видов файлов

Рис. 9.12. Динамические характеристики пирамидальной сортировки для различных видов файлов

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

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

Относительные значения времени выполнения различных видов сортировки на файлах случайных целых чисел соответствуют предположениям, основанным на оценке длины внутренних циклов: пирамидальная сортировка медленнее быстрой сортировки, но сопоставима по времени выполнения с сортировкой слиянием. Временные показатели для первых N слов книги " Моби Дик " в правой части таблицы показывают, что при больших затратах на операции сравнения метод Флойда является эффективным вариантом пирамидальной сортировки.

Таблица 9.2. Эмпирическое сравнение алгоритмов пирамидальной сортировки
N Q 32-битовые целые ключи Cтроковые ключи
M PQ H F Q H F
12500 2 5 4 3 4 8 11 8
25000 7 11 9 8 8 16 25 20
50000 13 24 22 18 19 36 60 49
100000 27 52 47 42 46 88 143 116
200000 58 111 106 100 107
400000 122 238 245 232 246
800000 261 520 643 542 566
Обозначения:
Q Быстрая сортировка, стандартная реализация (программа 7.1)
M Сортировка слиянием, стандартная реализация (программа 8.1)
PQ Пирамидальная сортировка на базе очереди с приоритетами (программа 9.5)
H Пирамидальная сортировка, стандартная реализация (программа 9.6)
F Пирамидальная сортировка с усовершенствованием Флойда

Упражнения

9.28. Покажите, что пирамидальная сортировка не является устойчивой.

9.29. Определите эмпирическим путем процент времени, затрачиваемого пирамидальной сортировкой на этап построения для N = 103, 104, 105 и 106.

9.30. Реализуйте версию пирамидальной сортировки, основанную на полных пирамидально упорядоченных тернарных деревьях, как описано в тексте. Сравните количество использованных операций сравнения, полученное эмпирическим путем, с аналогичным показателем стандартной реализации для N = 103, 104, 105 и 106.

9.31. Продолжая упражнение 9.30, определите эмпирическим путем, будет ли метод Флойда эффективен для тернарных деревьев.

9.32. Учитывая только стоимость сравнений и считая, что для определения наибольшего из t элементов требуется t операций сравнения, найдите значение t, которое сводит к минимуму коэффициент при Nlog N, присутствующий при подсчете операций сравнения, если в пирамидальной сортировке используется t-арное пирамидальное дерево. Сначала воспользуйтесь прямым обобщением программы 9.7, а затем учтите, что метод Флойда позволяет сэкономить одно сравнение во внутреннем цикле.

9.33. Для N = 32 найдите последовательность ключей, требующую выполнения пирамидальной сортировкой максимального количества сравнений.

9.34. Для N = 32 найдите последовательность ключей, требующую выполнения пирамидальной сортировкой минимального количества сравнений.

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

9.36. Реализуйте обе версии выборки на базе пирамидальной сортировки, о которых шла речь при обсуждении леммы 9.6, воспользовавшись методом, описанным в упражнении 9.25. Определите эмпирическим путем количество используемых ими сравнений и сравните его с аналогичным показателем метода на базе быстрой сортировки из "Быстрая сортировка" , для N = 106 и при к = 10, 100, 1000, 104, 105 и 106.

9.37. Реализуйте версию пирамидальной сортировки, основанную на идее представления пирамидально упорядоченного дерева в прямом порядке, а не по уровням. Проведите эмпирическое сравнение количества операций сравнения, используемых этой версией, с количеством сравнений в стандартной реализации для случайно упорядоченных ключей при N = 103, 104, 105 и 106.

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

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

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

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

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

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