Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Структурные элементы данных алгоритмы поиска и сортировки. Учебное пособие для СПО

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
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.
Если просмотр одного файла заканчивается раньше, чем просмотр друго-
го (по причине разного числа серий), то остаток непустого файла целиком ко-
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]