Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
redakt.docx
Скачиваний:
3
Добавлен:
01.05.2025
Размер:
401.52 Кб
Скачать

Вопрос 23. Пирамидальная сортировка

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

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

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], нужно:

  1. Добавить элемент x[i] слева к массиву уже имеющейся пирамиды

  2. Найти максимального из его сыновей: maxChild = max( x[2*i+1], x[2*i+2] )

  3. Если x[i] >= maxChild - завершение процедуры, элемент занимает положенное ему место, добавление завершено, иначе - шаг 4.

  4. maxChild > x[i] - поменять их местами (т.е. поменять местами x[i] и его большего ребенка).

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

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

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

На каждом шаге этого цикла мы продвигаемся на 2*ij позиций вперед по направлению к концу пирамиды(массивы), где j - номер итерации цикла.

Пр. Построим пирамиду для последовательности. x = [22, 100, 44, 15, 2, 36, 53, 23, 82, 5]. Делим пополам: 22, 100, 44, 15, 2 | 36, 53, 23, 82, 5. Алгоритм:

  1. Устанавливаем границы пирамиды c 0 по n-1, т.е. на данном этапе вся пирамида x[0..n-1]. А вершина(heap) = 0 и конец(tail) = n-1

  2. Меняем x[heap] и x[tail]

  3. Корректируем tail = tail - 1

  4. Просеиваем новую вершину x[0] через пирамиду x[1..tail].

  5. Если tail = 1, то конец сортировки, иначе - п.2.

Для нашей построенной в предыдущем примере пирамиды, получим:

  1. Обмен x[0]=100, x[9]=2; просеивание x[0]=2 через [82 53 23 5 36 44 22 15] Получим: [82 23 53 22 5 36 44 2 15 100]*.

  2. Обмен x[0]=82 и x[8]=15, просеивание x[0]=15 через [23 53 22 5 36 44 2] Получим: [53 23 44 22 5 36 15 2 82 100]*

  3. Обмен x[0]=53 и x[7]=2, просеивание x[0]=53 через [23 44 22 5 36 15] Получим: [44 23 36 22 5 2 15 53 82 100]*

  4. Обмен x[0]=44 и x[6]=15, просеивание x[0]=44 через [23 36 22 5 2] Получим: [36 23 15 22 5 2 44 53 82 100]*

  5. Обмен x[0]=36 и x[5]=2, просеивание x[0]=2 через [23 15 22 5] Получим: [23 22 15 2 5 36 44 53 82 100]*

  6. Обмен x[0]=23 и x[4]=5, просеивание x[0]=5 через [23 15 2] Получим: [22 5 15 2 23 36 44 53 82 100]*

  7. Обмен x[0]=22 и x[3]=2, просеивание x[0]=2 через [5 15] Получим: [15 5 2 22 23 36 44 53 82 100]*

  8. Обмен x[0]=15 и x[2]=2, просеивание x[0]=2 через [5]. В данном случае все просеивание сведется к обмену x[0]=2 и x[1]=5. Получим: [5 2 15 22 23 36 44 53 82 100]*

  9. Обмен x[0]=2 и x[1]=5. Просеивание делать уже не имеет смысла. Однако если в конкретной реализации данного алгоритма оно запускается для такого случая - то процедура просеивания должна отличать такую ситуации и не делать никаких перестановок.

В результате получили искомую сортировку: x=[2 5 15 22 23 36 44 53 82 100], что и требовалось.

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

Вопрос 24. Унифицированный язык моделирования - Unified Modeling Language (UML) обсудить >>

Пирамидальная сортировка, балансировка пирамиды, просеивание элементов через пирамиду, C++, code #29 ссылка +  рейтинг: 7/3,4.76(931), управление:

  1. /* Функция "балансировки" пирамиды.

  2. (просеивания, добавления элементов) */

  3. template<class T>

  4. void Screening(T* x, int k, int n) {

  5. /* Это чтобы при k=0 и n=0 не делалось лишней

  6. перестановки*/

  7. if (0 == n) return;

  8. T tmp;

  9. int childPos;

  10. tmp = x[k];

  11. while (k <= n/2) {

  12. childPos = 2*k; // Левый ребенок элемента k

  13. /* выбираем большего ребенка элемента k

  14. из 2-х: либо левый, либо правый

  15. */

  16. if (childPos < n && x[childPos] < x[childPos + 1]) {

  17. childPos++;

  18. }

  19. /* Если родитель x[k] больше всех своих детей,

  20. то ничего не делаем, он стоит на правильном месте */

  21. if(tmp >= x[childPos]) break;

  22. // иначе - меняем x[k] с наибольшим ребенком

  23. x[k] = x[childPos];

  24. k = childPos;

  25. }

  26. x[k] = tmp;

  27. }

  28. template<class T>

  29. void PyramidSort1(T* x, int n) {

  30. int i;

  31. T tmp;

  32. // Построение пирамиды

  33. for (i = n/2; i >= 0; i--) {

  34. Screening(x, i, n-1);

  35. }

  36. /* Формирование конечной отсортированной

  37. последовательности + "балансирование"

  38. пирамиды */

  39. for (i = n-1; i > 0; i--) {

  40. // меняем первый с последним

  41. swap(x, 0, i);

  42. /* Восстановление баланса

  43. для пирамиды x[0..i-2] */

  44. Screening(x, 0, i-1);

  45. }

  46. }

Как видно, алгоритм разбит на 2 важные задачи: функция балансировки пирамиды и собственно сама сортировка. Производительность: ~O(n*log n) Расход памяти: - (только счетчики цикла, доп. переменные, вызов функции балансировки)

Тестировалось на: MS Visual Studio 2005, .NET Framework 2.0

обсудить >>

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

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

Первый уровень - это сами данные.

Второй - это их модель, т. е., например, описание их в программе.

Третий - метамодель, т. е. описание языка построения модели.

Четвертый - мета-метамодель, т. е. описание языка, на котором описана метамодель. Для примера - следующий рисунок, позаимствованный из стандарта UML, показывает применение этого подхода к простым записям о котировках акций

Рисунок 4 – Пример четырехуровнего моделирования

Метамодель - описание самого языка, мета-метамодель - описание формализма, с помощью которого производится описание языка.

Все это сопровождается комментариями на естественном языке и примерами моделей.

Выводы

• UML - еще один формальный язык, который необходимо освоить каждому, кто собирается заниматься программной инженерией.

• Само собой разумеется, что знание UML не гарантирует построения разумных и понятных моделей, хотя и является для этого необходимым.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]