Суть метода
Теперь весь метод в целом. Вначале имеем исходную нашу неотсортированную последовательность. Из нее строится вышеупомянутая структура данных - пирамида. Пирамида обладает тем свойством, что в ее вершине - находится максимальный элемент. Кроме того вершина - это элемент в структуре пирамиды с начальным индексом. Поэтому чтобы выбрать из пирамиды максимальный элемент - не нужно делать вообще ничего - нужно просто взять этот начальный элемент стуктуры пирамиды.
Пирамида построена, далее можно начинать основные итерации сортировки. На каждой итерации: 1. Вынимаем вершину пирамиды 2. На ее место вставляем последний в пирамиде элемент 3. "Просеиваем" этот элемент сквозь пирамиду.
И так до последней итерации на которой из всей пирамиды останется только вершина, которую мы и вставим в конец нашей полученной отсортированной последовательности.
Просеивание
Неясным остался момент "просеивания" элемента сквозь пирамиду. Под этим подразумевается восстановление баланса пирамиды. После того как мы ставим последний элемент на вершину - пирамида теряет свои свойства, становится "разбалансированной". Поэтому дальше нужно этот элемент поставить в соответсвующее ему место, а на вершину восстановить максимальный элемент.
Так работает пирамидальная сортировка: перед сортировкой строится пирамида, на каждой итерации мы вынимаем из нее максимальный элемент и ставим в свою отсортированную последовательность, и далее восстанавливаем баланс пирамиды.
Структура пирамиды
Теперь рассмотрим наконец что же представляет из себя эта пирамида. Это бинарное дерево, в котором каждый элемент меньше либо равен его родителю.
Например, имеем исходную последовательность: x = [22, 100, 44, 15, 2, 36, 53, 23, 82, 5] Пирамида для нее будет выглядеть как:
Хранить пирамиду удобно в виде массиве. Нумерацию элементов пирамиды определяем просто: проходимся сверху вниз, слева направо по каждому элементу и нумеруем его. В итоге получим то, что изображено на рисунке(цифры черным цветом - искомые индексы).
Т.о. получим x[0] - вершина пирамиды, а левый и правый потомки каждого элемента x[i] будут соответственно: x[2*i+1] и x[2*i+2] и основное свойство пирамиды тогда можно записать как:
x[i] >= max( x[2*i+1], x[2*i+2] )
Построение пирамиды
Исходный массив делится пополам, вторая его половина уже принимается за пирамиду. Затем последовательно берутся элементы из первой половины и добавляются в пирамиду.
Может возникнуть вопрос: с чего это вдруг мы сразу пол-массива считаем за пирамиду, а не строим ее шаг за шагом начиная с последнего элемента, добавляя туда потом предпоследний и т.д. до первого элемента? Дело все в том, что для второй половины нашего исходного массиваосновное свойство пирамиды - выполняется автоматически. Вернее, оно не нарушается, поскольку для элементов второй половины просто уже не будет потомков!!! Действительно: потомки для всех x[i], где i = n/2..n будут соответственно x[n+1]...x[2*n+2], т.е. такие, которых в нашем массиве уже нет. А поэтому нет смысла для элементов второй половины строить пирамиду последовательно добавляя каждый элемент, т.к. в алгоритме текущий добавляемый элемент просто будет не с чем сравнивать, сыновей-то с такими индексами - просто нет!
Так что спокойно принимаем вторую половину нашей последовательности как пирамиду и приступаем к следующему этапу построения - добавлению элементов.
Чтобы добавить элемент в пирамиду - нужно поменять его с наибольшим ребенком, если последний превосходит добавляемый элемент. Затем тоже самое по отношению уже к новым его детям. Т.о. чтобы добавить в пирамиду новый элемент x[i], нужно:
Добавить элемент x[i] слева к массиву уже имеющейся пирамиды
Найти максимального из его сыновей: maxChild = max( x[2*i+1], x[2*i+2] )
Если x[i] >= maxChild - завершение процедуры, элемент занимает положенное ему место, добавление завершено, иначе - шаг 4.
maxChild > x[i] - поменять их местами(т.е. поменять местами x[i] и его большего ребенка). Перейти к шагу 2, учитывая новое положение добавляемого элемента.
При этом, как можно догадаться - просеивание, о котором велась речь выше - это тоже добавление, поскольку после удаления максимального элемента, т.е. вершины пирамиды, мы ставим на его место последний элемент, а это по-сути - опять же добавление элемента в пирамиду. Поэтому для просеивания нам не нужно писать еще одну функцию, мы просто вызываем добавление по-отношению к этому новому элементу. В итоге имеем лишь единственную процедуру, которую можно назвать общим понятием для этих двух действий - назовем ее "восстановлением баланса пирамиды".
Выглядеть она будет примерно следующим образом:
псевдокод: Восстановление баланса пирамиды при добавлении нового x[k] ссылка
/* алгоритм процедуры восстановления баланса пирамиды
* при добавлении нового x[k]
*
* В x[] - исходный массив
* n - количество его элементов
* k - индекс добавляемого/просеиваемого элемента
*/
/* Это чтобы при k=0 и n=0
не делалось лишней перестановки*/
if 0 = n
exit
tmp = x[i] // Оставляем копию
while k <= n/2
childPos = 2*k+1 // Индекс левого ребенка
/* выбираем в childPos индекс большего ребенка */
if (childPos < n) && (x[childPos] < x[childPos + 1])
childPos++
/* Если x[k] больше максимального ребенка -
завершение */
if(tmp >= x[childPos]) break;
// иначе - меняем его с наибольшим ребенком
x[k] = x[childPos];
k = childPos;
}
/* В конце восстанавливаем просеянному элементу
его первоначальное значение */
x[k] = tmp;
На каждом шаге этого цикла мы продвигаемся на 2*ij позиций вперед по направлению к концу пирамиды(массивы), где j - номер итерации цикла. Т.о. данная процедура займет порядка ~ O(log n) времени.
Имея такую функцию процедура построения пирамиды займет...1(!!) строчку:
псевдокод: построение пирамиды ссылка
/* Screening(x, k, n) - функция восстановления
баланса пирамиды */
for i = n/2 downto 0
Screening(x, i, n-1);
В итоге получаем производительность процедуры построения порядка ~O(n*log n).
Пример. Построим пирамиду для последовательности x = [22, 100, 44, 15, 2, 36, 53, 23, 82, 5]. Делим пополам: 22, 100, 44, 15, 2 | 36, 53, 23, 82, 5. То что справа уже пирамида, слева - то, что будет просеиваться через нее. Т.о. получим(темные элементы - пирамида, светлые - что предстоит просеивать):
Шаг. Просеиваем x[4] = 2 через пирамиду [36, 53, 23, 82, 5] Дети этого элемента, вернее только один: x[9] = 5. 5>2 => меняем местами x[4], x[9]. Далее дети x[9] - нет уже => итерация завершена, новая пирамида: [5, 36, 53, 23, 82, 2]
Шаг. Просеиваем x[3]=15 через [5, 36, 53, 23, 82, 2]. Дети: x[7]=23, x[8]=82. x[8] - наибольший. Т.к. x[8]=82 > x[3]=15 => Обмениваем x[3],x[8]. У x[8] - детей уже нет => итерация завершена, новая пирамида: [82, 5, 36, 53, 23, 15, 2]
Шаг. Просеиваем x[2]=44 через [82, 5, 36, 53, 23, 15, 2]. Здесь, аналогично, получаем: обмен x[2],x[6], новая пирамида [53, 82, 5, 36, 44, 23, 15, 2]
Шаг. Просеиваем x[1]=100 через [53, 82, 5, 36, 44, 23, 15, 2]. x[1] больше максимального(x[3]=82) своего ребенка. Итерация завершается без обменов. Новая пирамида: [100, 53, 82, 5, 36, 44, 23, 15, 2]
Шаг. Просеиваем x[0]=22 через [100, 53, 82, 5, 36, 44, 23, 15, 2]. Наибольший ребенок x[1]=100 > x[0] => обмениваем x[0],x[1]. Получаем [100, 22, 53, 82, 5, 36, 44, 23, 15, 2]:
Далее
согласно алгоритму,
смотрим детей уже для нового x[1] =
22: x[3]=82,x[4]=5.
Максимальный
x[3]=82 > x[1]=22 => меням x[1],x[3].
Получили
[100, 82, 53, 22, 5, 36, 44, 23, 15, 2]:
Опять
смотрим детей уже для нового
x[3]=22: x[7]=23,
x[8]=15.
Максимальный x[7]>x[3] => обмен
x[7],x[3].
Получили [100, 82, 53, 23, 5, 36, 44, 22,
15, 2]:
У
x[7] - детей уже нет => итерация завершена.
В результате получили построенную пирамиду. Особо следует обратить внимание на Шаг 5, в котором вершина 22 просеилась в 3 прохода до самого последнего уровня, в отличие от других шагов, где имела место максимум 1 перестановка.
