Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Структурные элементы данных алгоритмы поиска и сортировки. Учебное пособие для СПО
.pdf
31
ководство добавляет задачи в список, указывая их приоритет, то есть выполняет операцию добавления элемента.
К основным операциям над очередью с приоритетом относятся:
− создать очередь с приоритетами из N заданных элементов;
− вставить новый элемент;
− извлечь наибольший элемент;
− изменить приоритет некоторого заданного элемента;
− извлечь некоторый заданный элемент;
− объединить две очереди с приоритетами в одну.
1.7.1. Пирамидальная структура данных
Простая структура данных, получившая название пирамидального дере-
ва, позволяет эффективно выполнять основные операции над очередями с при-
оритетами.
Записи пирамидального дерева хранятся в массиве таким образом, что
каждый ключ обязательно больше, чем значения ключей в двух других заданных позициях. В свою очередь, каждый из этих ключей должен быть больше,
чем два других ключа, и т. д. Такое упорядочение легко представить наглядно,
если рассматривать эти ключи как входящие в бинарную древовидную структуру с ребрами, идущими от каждого ключа к двум меньшим его ключам.
ВНИМАНИЕ! Значения хранятся в массиве. Дерево — это логическое
представление пирамидальной структуры данных, позволяющее наглядно представлять эту структуру хранения.
Дерево называется пирамидально упорядоченным, если ключ в каждом
его узле больше (или равен) ключей всех дочерних узлов этого узла (если они
есть). Эквивалентная формулировка: ключ в каждом узле пирамидально упорядоченного дерева меньше (или равен) ключа узла, который является родителем
данного узла (если он есть).
Никакой узел пирамидально упорядоченного дерева не может иметь ключ
больший, чем ключ в корне дерева.
Представление пирамидальной структуры данных в виде массива и бинарного дерева приведено на рис. 1.15. Еще раз напоминаю, что реально данные хранятся в массиве. Бинарное представление необходимо лишь для лучшего понимания основных операций над этой структурой данных.

32
Рис. 1.15. Пирамидальная структура данных
Исходя из вышеизложенного, родитель узла в позиции i находится в позиции i / 2 и, соответственно, два потомка узла в позиции i находятся в позициях 2i и 2i + 1. Такая организация данных позволяет достаточно легко продвигаться по дереву как сверху вниз, так и снизу вверх.
Пирамидальные деревья позволяют реализовать все операции над очередями с приоритетами (за исключением операции объединения) таким образом,
что на свое выполнение они потребуют логарифмическое время в худшем
случае.
1.7.2. Алгоритмы на пирамидальных деревьях
Все алгоритмы очередей с приоритетами на основе пирамидальных деревьев работают таким образом: сначала вносится простое изменение, которое
может нарушить структуру дерева, а затем выполняется обход дерева с такими
изменениями, чтобы оно снова стало пирамидальным.
Этот процесс иногда называют установлением пирамидальности или просто восстановлением пирамидального дерева.
Возможны два случая. Если приоритет (значение) какого-либо узла увеличивается (или в нижний уровень пирамидального дерева добавляется новый
узел), то для восстановления структуры пирамидального дерева нужно двигаться вверх по дереву.
Если приоритет какого-либо узла уменьшается (например, при замене
корневого узла новым узлом), то для восстановления структуры пирамидального дерева нужен проход вниз по дереву.
Если пирамидальность дерева нарушена из-за того, что ключ некоторого
узла стал больше ключа родительского узла, можно попытаться исправить это
нарушение, обменяв местами этот узел с его родителем. После обмена этот узел
становится больше, чем оба его потомка (один из них — это прежний родитель,

33
а другой меньше, чем старый родитель, поскольку он был потомком этого узла), но все еще может оставаться больше своего родителя. Это нарушение можно исправить аналогичным способом — продвигаясь далее вверх по дереву, пока не будет достигнут либо узел с большим ключом, либо корень.
Пусть в пирамидальном дереве на рис. 1.16 приоритет узла со значением
15 увеличился до 99. Тогда узел 99 поменяется сначала местами с узлом 77, затем с узлом 83 и, наконец, с узлом 98.
Рис. 1.16. Начальное положение пирамидального дерева
Результат повышения приоритета приведен на рис. 1.17.
Рис. 1.17. Результат повышения приоритета
Код, реализующий данную операцию, примитивен, поскольку основан на
том, что родитель узла, занимающего в пирамидальном дереве позицию k,
находится в позиции k / 2:
Процедура ВВЕРХ(A, k)
Пока (k>1 и a[k/2] < a[k])
ОБМЕН(a[k], a[k/2])
k = k / 2,

34
где A — массив, в котором хранится пирамидальное дерево;
k — индекс элемента массива, в котором повышен приоритет.
Если же пирамидальность дерева была нарушена из-за того, что ключ какого-либо узла стал меньше одного или обоих ключей его потомков, можно попытаться устранить это нарушение, обменяв узел с бóльшим из его двух потомков. Такой обмен способен вызвать нарушение пирамидальности дерева на узле-потомке, однако его можно устранить таким же способом и двигаться вниз
по дереву до достижения либо узла, оба потомка которого меньше его самого,
либо нижнего уровня дерева.
Пусть в пирамидальном дереве на рис. 18 приоритет узла со значением 96
уменьшился до 13. Тогда узел 13 поменяется местами с максимальным по значению потомком — узлом 81, а затем и с узлом 16. Результат понижения приоритета приведен на рис. 1.18.
Рис. 1.18. Результат понижения приоритета
Код, реализующий данную операцию, также основан на том факте, что
потомки узла пирамидального дерева в позиции k находятся в позициях 2k
и 2k + 1. Этой функции должен быть известен размер пирамидального дерева N,
чтобы определить момент достижения нижнего уровня дерева.
Процедура ВНИЗ(A, k, N)
Пока (2k <= N)
j = 2k
Если (j < N && a[j] < a[j + 1]) j=j+1 // В j индекс бóльшего потомка
Если (a[k] ≥ a[j]) Выход // Оба потомка меньше
ОБМЕН(a[k], a[j])
k = j,
где A — массив, в котором хранится пирамидальное дерево;
k — индекс элемента массива, в котором понижен приоритет;
N — размерность массива.

35
1.7.3. Добавление элементов в пирамидальное дерево
и их удаление из него
Рассмотренные выше две основные операции позволяют эффективно реа-
лизовать тип данных «очереди с приоритетами».
Если очередь с приоритетами представлена как пирамидально упорядо-
ченный массив, то операция «Вставить» сводится к добавлению нового элемента в конец массива и перемещению этого элемента вверх по массиву для
восстановления пирамидальности, а операция «Извлечь наибольший» —
к удалению наибольшего элемента из вершины дерева (первого элемента массива) с последующей пересылкой элемента из конца дерева в его вершину (последнего элемента массива на первое место массива) и перемещением его вниз
по массиву для восстановления пирамидальной структуры.
При выполнении операции «Вставить» мы увеличиваем N на 1, добавляем новый элемент в конец массива, а затем вызываем процедуру ВВЕРХ для
восстановления пирамидальности.
Для операции «Извлечь наибольший» мы выбираем в качестве возвращаемого значения значение А[1], переносим значение А[N] в А[1], после чего
уменьшаем размер дерева N на 1, а затем вызываем процедуру ВНИЗ для восстановления пирамидальности.
ВНИМАНИЕ! Нулевая позиция А[0] массива не используется и может
быть задействована для других целей.
1.8. ПИРАМИДАЛЬНАЯ СОРТИРОВКА
Основываясь на материале седьмого раздела, можно предложить следующий алгоритм пирамидальной сортировки [5].
Пусть задан исходный массив А. Сформировать пирамидально упорядоченный массив В, последовательно выполняя для каждого элемента массива А
описанную в седьмом разделе операцию «Вставить».
С помощью многократного применения к массиву В описанной в разделе
семь операции «Извлечь наибольший» получить упорядоченный массив А.
Однако данный подход имеет следующие недостатки:
– он требует дополнительной памяти, размер которой равен размеру исходного массива;
– полученный в результате упорядоченный массив упорядочен по убыванию, что не всегда удовлетворяет поставленной при сортировке задаче.
Ниже рассмотрен подход, решающий описанные выше проблемы.

36
1.8.1. Организация пирамидального дерева
внутри сортируемого массива
Идею, рассмотренную в предыдущем разделе, можно модифицировать
так, чтобы сортировка массива не требовала дополнительной памяти, — организовав пирамидальное дерево внутри сортируемого массива.
Перебирая узлы справа налево (для массива) и снизу вверх (в представле-
нии дерева), будем строить пирамидальное дерево, обеспечивая пирамидальную упорядоченность ниже текущего узла. Иными словами, вместо построения
пирамидального дерева последовательными вставками, как было предложено
в разделе 8.1, эффективнее создать это дерево с помощью прохода в обратном
направлении.
При этом каждая позиция массива рассматривается как корень небольшо-
го поддерева и используется тот факт, что функция ВНИЗ() работает на таких
поддеревьях так же хорошо, как и на большом дереве. Если оба потомка узла
являются пирамидальными деревьями, то после вызова функции ВНИЗ() поддерево с корнем в этом узле также становится пирамидальным деревом. Проходя по дереву в обратном направлении и вызывая функцию ВНИЗ() в каждом
узле, мы по индукции устанавливаем пирамидальный порядок.
ВНИМАНИЕ! Обратный проход вдоль массива лучше начать с полпути,
поскольку поддеревья, состоящие из одного узла, можно пропустить, ввиду того что в правой половине массива находятся только листья дерева.
Другими словами, для построения пирамидально упорядоченного массива на месте исходного достаточно пройти по исходному массиву от элемента
с индексом (N – 1) / 2 до 1-го элемента и для каждого из них выполнить функцию ВНИЗ().
1.8.2. Формирование упорядоченного по возрастанию массива
Как изложено в разделе 7, операция «Извлечь наибольший» предполагает выборку из массива элемента с индексом 1, а затем вставки на первое место последнего элемента массива с последующим уменьшением размерности
массива на единицу и выполнением для нового первого элемента массива
функции ВНИЗ().
Для того чтобы результирующий массив был отсортирован по возрастанию и занимал ту же память, что и исходный массив, достаточно в описанном
выше алгоритме просто менять местами первый элемент и последний в остав-

37
шейся неотсортированной последовательности. Тогда первый наибольший элемент пойдет на последнее место, второй — на предпоследнее и т. д.
Таким образом будет получен упорядоченный по возрастанию массив
в исходном массиве А.
1.9. АЛГОРИТМЫ ВНЕШНЕЙ СОРТИРОВКИ
К последовательному файлу нельзя непосредственно применить метод
внутренней сортировки (сортировки массивов). Это объясняется тем, что в последовательном файле в каждый момент имеется доступ только к одному элементу. Это строгое ограничение по сравнению с возможностями, которые дает
массив, поэтому для файлов приходится применять иные методы сортировки.
Разумеется, в этом случае, когда размер файла невелик и объем оперативной памяти достаточен для его размещения, можно прочитать файл в память,
отсортировать полученный массив одним из методов внутренней сортировки,
а затем отсортированный массив записать в файл. Однако это не является типичным случаем. Более того, в системах обработки данных такая ситуация
встречается крайне редко.
Далее мы рассмотрим общий случай, когда сортируемый файл имеет значительно больший объем, чем объем, доступный оперативной памяти. Такой
файл сортируется поэтапно.
Основным понятием при использовании внешней сортировки является
понятие серии. Серия (упорядоченный отрезок) — это последовательность
элементов, которая упорядочена по ключу.
Количество элементов в серии называется длиной серии. Серия, состоящая из одного элемента, упорядочена всегда. Последняя серия может иметь
длину меньшую, чем остальные серии файлов. Максимальное количество серий — в файле N (все элементы не упорядочены). Минимальное количество серий — одна (все элементы упорядочены).
В основе большинства методов внешних сортировок лежит процедура
слияния и процедура распределения.
Слияние — это процесс объединения двух (или более) упорядоченных
серий в одну упорядоченную последовательность при помощи циклического
выбора элементов, доступных в данный момент [4].
Распределение — это процесс разделения упорядоченных серий на два
и несколько вспомогательных файла.
Фаза — это действия по однократной обработке всей последовательности
элементов.

38
Двухфазная сортировка — это сортировка, в которой отдельно реализуется две фазы: распределение и слияние.
Однофазная сортировка — это сортировка, в которой объединены фазы
распределения и слияния в одну.
Одним из наиболее привлекательных свойств сортировки слиянием является тот факт, что она сортирует файл, состоящий из N элементов, за время,
пропорциональное N × logN, независимо от характера входных данных.
Основной недостаток сортировки слиянием заключается в том, что простые реализации этого алгоритма требуют объема дополнительной памяти,
пропорционального N.
Сортировка слиянием является устойчивой, и это склоняет чашу весов
в ее пользу в тех приложениях, в которых устойчивость важна.
У сортировки слиянием есть еще одно (иногда очень важное) свойство:
она обычно реализуется таким образом, что обращается к данным в основном
последовательно (один элемент за другим). Поэтому сортировка слиянием
удобна для упорядочения связных списков, для которых применим только метод последовательного доступа.
1.9.1. Двухфазная сортировка простым слиянием
Одна из сортировок на основе слияния называется сортировкой простым слиянием [3]. Она выполняется следующим образом:
1. Файл A разбивается на два файла: B и C. При этом первый элемент
файла A помещается в файл B, второй элемент — в файл C, третий — в файл B,
четвертый — в файл C и т. д.
2. Файлы B и C сливаются в файл A, при этом одиночные элементы образуют упорядоченные пары.
3. Полученный файл A вновь обрабатывается, как указано в п. 1 и 2, при
этом упорядоченные пары переходят в упорядоченные четверки.
4. Повторяя предыдущие шаги, сливаем четверки в восьмерки и т. д.,
каждый раз удваивая длину слитых последовательностей до тех пор, пока длина
серии не превысит размер файла.
Признаками конца сортировки простым слиянием являются следую-
щие условия:
– длина серии не меньше количества элементов в файле,
– количество серий равно 1 (определяется на фазе слияния),
– при однофазной сортировке второй по счету вспомогательный файл по-
сле распределения серий остался пустым.

39
Рассмотрим работу алгоритма на примере (рис. 1.19). Пусть задан файл A.
A — 55
12
87
76
98
24
84
27 B —
55
87
98
84 C —
12
76
24
27 A —
12
55
76
87
24
98
27
84 B —
12
55
24
98 C —
76
87
27
84
A — 12
55
76
87
24
27
84
98
B — 12
55
76
87
C — 24
27
84
98
A — 12
24
27
55
76
84
87
98
Рис. 1.19. Двухфазная сортировка простым слиянием
Поскольку на каждом проходе размерность серии удваивается, то сорти-
ровка заканчивается, когда p n, где р — размер серии, а n — размер исходного файла.
Количество проходов: k = [log2n]. Файлы A, B и C будут O(log2n) раз про-
читаны и столько же раз записаны. По определению при каждом проходе все
множество из n элементов копируется ровно 1 раз. Следовательно, общее число
пересылок М = n × [log2n].
Число сравнений С еще меньше, чем М, так как при копировании остатка
последовательности сравнения не производятся. Поскольку сортировка слиянием использует ВЗУ, то временные затраты на операцию пересылки на несколько порядков превышают временные затраты на операции сравнения.
Для выполнения внешней сортировки методом прямого слияния в основ-
ной памяти требуется расположить всего лишь две переменные: для размещения очередных записей из файлов B и C.
Минусом сортировки слиянием является удвоение размера памяти, пер-
воначально занятой сортируемыми данными.
Рассмотренная выше сортировка состоит из фазы разделения и фазы сли-
яния, поэтому она называется двухфазной сортировкой простым слиянием.
1.9.2. Однофазная сортировка простым слиянием
Фаза разделения файла A на файлы B и C не относится к сортировке. Она
непродуктивна, хотя и занимает половину всех операций по чтению/записи.

40
Очевидным улучшением (по быстродействию, но не по занимаемой памя-
ти) рассмотренного выше алгоритма является объединение фазы разделения
с фазой слияния.
Вместо слияния в один файл результаты слияния необходимо сразу рас-
пределять по двум файлам, которые станут исходными для последующего прохода.
Такая сортировка называется однофазной сортировкой простым слиянием.
Рассмотрим выполнение однофазной сортировки на примере (рис. 1.20).
Пусть задан файл A:
A — 55
12
87
76
98
24
84
27 B —
55
87
98
84
C — 12
76
24
27
D — 12
55
24
98
E — 76
87
27
84 B —
12
55
76
87 C —
24
27
84
98 A — 12
24
27
55
76
84
87
98
Рис. 1.20. Однофазная сортировка простым слиянием
Очевидно, что для такой сортировки требуется уже не два, а четыре до-
полнительных файла.
1.9.3. Сортировка естественным слиянием
В случае простого слияния мы не получаем никакого преимущества, если
данные уже являются частично упорядоченными. Размер сливаемых при каждом проходе последовательностей не зависит от существования более длинных
уже упорядоченных последовательностей, которые можно было бы просто объединить.
Сортировка, при которой всегда сливаются две самые длинные из воз-
можных упорядоченных последовательностей, называется естественным сли-
янием.
Пусть имеется начальный файл A. Каждый проход состоит из фазы рас-
пределения серий из файла A поровну в файлы B и C и фазы слияния, объединяющей серии из файлов B и C в файл A.
Если просмотр одного файла заканчивается раньше, чем просмотр друго-
го (по причине разного числа серий), то остаток непустого файла целиком ко-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
