Файл: Алгоритмы сортировки данных (Типы и структуры данных).pdf
Добавлен: 18.05.2023
Просмотров: 433
Скачиваний: 3
СОДЕРЖАНИЕ
3.1. Оценка алгоритма сортировки
4. Методы внутренней сортировки
4.1. Сортировка включением (метод Шелла)
4.2. Обменная сортировка (метод Пузырька и Шейкерная сортировка)
4.4. Сортировка разделением (Quicksort)
4.5. Сортировка с помощью дерева (Heapsort)
4.7. Сравнение методов внутренней сортировки
4.8. Общий анализ приведенных сортировок
4.9. Теоретическое сравнение сортировок методом простых вставок и методом пузырька

Рисунок. 4.5.2 Второй шаг

Рисунок. 4.5.3 Третий шаг

Рисунок. 4.5.4 четвертый шаг

Рисунок. 4.5.5 Пятый шаг

Рисунок. 4.5.6 Шестой шаг

Рисунок. 4.5.7 Седьмой шаг

Рисунок. 4.5.8 Восьмой шаг
На каждом из n шагов, требуемых для сортировки массива, нужно log n (двоичный) сравнений. Следовательно, всего потребуется n?log n сравнений, но для представления дерева понадобится 2n - 1 дополнительных единиц памяти.
Имеется более совершенный алгоритм, который принято называть пирамидальной сортировкой (Heapsort). Его идея состоит в том, что вместо полного дерева сравнения исходный массив a[1], a[2], ..., a[n] преобразуется в пирамиду, обладающую тем свойством, что для каждого a[i] выполняются условия a[i] <= a[2i] и a[i] <= a[2i+1]. Затем пирамида используется для сортировки.
Наиболее наглядно метод построения пирамиды выглядит при древовидном представлении массива, показанном на рисунке 4.5.9. Массив представляется в виде двоичного дерева, корень которого соответствует элементу массива a[1]. На втором ярусе находятся элементы a[2] и a[3]. На третьем - a[4], a[5], a[6], a[7] и т.д. Как видно, для массива с нечетным количеством элементов соответствующее дерево будет сбалансированным, а для массива с четным количеством элементов n элемент a[n] будет единственным (самым левым) листом "почти" сбалансированного дерева.

Рисунок. 4.5.9 Построение пирамиды
Очевидно, что при построении пирамиды нас будут интересовать элементы a[n/2], a[n/2-1], ..., a[1] для массивов с четным числом элементов и элементы a[(n-1)/2], a[(n-1)/2-1], ..., a[1] для массивов с нечетным числом элементов (поскольку только для таких элементов существенны ограничения пирамиды). Пусть i - наибольший индекс из числа индексов элементов, для которых существенны ограничения пирамиды. Тогда берется элемент a[i] в построенном дереве и для него выполняется процедура просеивания, состоящая в том, что выбирается ветвь дерева, соответствующая min(a[2?i], a[2?i+1]), и значение a[i] меняется местами со значением соответствующего элемента. Если этот элемент не является листом дерева, для него выполняется аналогичная процедура и т.д. Такие действия выполняются последовательно для a[i], a[i-1], ..., a[1]. Легко видеть, что в результате мы получим древовидное представление пирамиды для исходного массива (последовательность шагов для используемого в наших примерах массива показана на рисунках 4.5.10-4.5.13).

Рисунок. 4.5.10

Рисунок. 4.5.11

Рисунок. 4.5.12

Рисунок. 4.5.13
В 1964 г. Флойд предложил метод построения пирамиды без явного построения дерева (хотя метод основан на тех же идеях). Построение пирамиды методом Флойда для нашего стандартного массива показано в таблице 4.5.1.
Таблица 4.5.1 Пример построения пирамиды
|
Начальное состояние массива |
8 23 5 |65| 44 33 1 6 |
|
Шаг 1 |
8 23 |5| 6 44 33 1 65 |
|
Шаг 2 |
8 |23| 1 6 44 33 5 65 |
|
Шаг 3 |
|8| 6 1 23 44 33 5 65 |
|
Шаг 4 |
1 6 8 23 44 33 5 65 1 6 5 23 44 33 8 65 |
В таблице 4.5.2 показано, как производится сортировка с использованием построенной пирамиды. Суть алгоритма заключается в следующем. Пусть i - наибольший индекс массива, для которого существенны условия пирамиды. Тогда начиная с a[1] до a[i] выполняются следующие действия. На каждом шаге выбирается последний элемент пирамиды (в нашем случае первым будет выбран элемент a[8]). Его значение меняется со значением a[1], после чего для a[1] выполняется просеивание. При этом на каждом шаге число элементов в пирамиде уменьшается на 1 (после первого шага в качестве элементов пирамиды рассматриваются a[1], a[2], ..., a[n-1]; после второго - a[1], a[2], ..., a[n-2] и т.д., пока в пирамиде не останется один элемент). Легко видеть (это иллюстрируется в таблице 4.5.2), что в результате мы получим массив, упорядоченный в порядке убывания. Можно модифицировать метод построения пирамиды и сортировки, чтобы получить упорядочение в порядке возрастания, если изменить условие пирамиды на a[i] >= a[2?i] и a[1] >= a[2?i+1] для всех осмысленных значений индекса i.
Таблица 4.5.2 Сортировка с помощью пирамиды
|
Исходная пирамида |
1 6 5 23 44 33 8 65 |
|
Шаг 1 |
65 6 5 23 44 33 8 1 5 6 65 23 44 33 8 1 5 6 8 23 44 33 65 1 |
|
Шаг 2 |
65 6 8 23 44 33 5 1 6 65 8 23 44 33 5 1 6 23 8 65 44 33 5 1 |
|
Шаг 3 |
33 23 8 65 44 6 5 1 8 23 33 65 44 6 5 1 |
|
Шаг 4 |
44 23 33 65 8 6 5 1 23 44 33 65 8 6 5 1 |
|
Шаг 5 |
65 44 33 23 8 6 5 1 33 44 65 23 8 6 5 1 |
|
Шаг 6 |
65 44 33 23 8 6 5 1 44 65 33 23 8 6 5 1 |
|
Шаг 7 |
65 44 33 23 8 6 5 1 |
Процедура сортировки с использованием пирамиды требует выполнения порядка nxlog n шагов (логарифм - двоичный) в худшем случае, что делает ее особо привлекательной для сортировки больших массивов.
Пирамидальная сортировка может рассматриваться как усовершенствованная сортировка пузырьком, в которой элемент всплывает (min-heap) / тонет (max-heap) по многим путям.
Достоинства:
- Имеет доказанную оценку худшего случая O(n * log n).
- Сортирует на месте, то есть требует всего O(1) дополнительной памяти (если дерево организовывать так, как показано выше).
Недостатки:
- Сложен в реализации.
- Неустойчив - для обеспечения устойчивости нужно расширять ключ.
- На почти отсортированных массивах работает столь же долго, как и на хаотических данных.
- На одном шаге выборку приходится делать хаотично по всей длине массива - поэтому алгоритм плохо сочетается с кэшированием и подкачкой памяти.
- Методу требуется «мгновенный» прямой доступ; не работает на связанных списках и других структурах памяти последовательного доступа.
- Сортировка слиянием при расходе памяти O(n) быстрее (O(n * log n) с меньшей константой) и не подвержена деградации на неудачных данных.
- Из-за сложности алгоритма выигрыш получается только на больших n. На небольших n (до нескольких тысяч) быстрее сортировка Шелла.
4.6. Сортировка со слиянием
Сортировки со слиянием, как правило, применяются в тех случаях, когда требуется отсортировать последовательный файл, не помещающийся целиком в основной памяти. Методам внешней сортировки посвящается следующая часть книги, в которой основное внимание будет уделяться методам минимизации числа обменов с внешней памятью. Однако существуют и эффективные методы внутренней сортировки, основанные на разбиениях и слияниях.
Один из популярных алгоритмов внутренней сортировки со слияниями основан на следующих идеях (для простоты будем считать, что число элементов в массиве, как и в нашем примере, является степенью числа 2). Сначала поясним, что такое слияние. Пусть имеются два отсортированных в порядке возрастания массива p[1], p[2], ..., p[n] и q[1], q[2], ..., q[n] и имеется пустой массив r[1], r[2], ..., r[2?n], который мы хотим заполнить значениями массивов p и q в порядке возрастания. Для слияния выполняются следующие действия: сравниваются p[1] и q[1], и меньшее из значений записывается в r[1]. Предположим, что это значение p[1]. Тогда p[2] сравнивается с q[1] и меньшее из значений заносится в r[2]. Предположим, что это значение q[1]. Тогда на следующем шаге сравниваются значения p[2] и q[2] и т.д., пока мы не достигнем границ одного из массивов. Тогда остаток другого массива просто дописывается в "хвост" массива r.
Пример слияния двух массивов показан на рисунке 4.6.1.

Рисунок. 4.6.1
Для сортировки со слиянием массива a[1], a[2], ..., a[n] заводится парный массив b[1], b[2], ..., b[n]. На первом шаге производится слияние a[1] и a[n] с размещением результата в b[1], b[2], слияние a[2] и a[n-1] с размещением результата в b[3], b[4], ..., слияние a[n/2] и a[n/2+1] с помещением результата в b[n-1], b[n]. На втором шаге производится слияние пар b[1], b[2] и b[n-1], b[n] с помещением результата в a[1], a[2], a[3], a[4], слияние пар b[3], b[4] и b[n-3], b[n-2] с помещением результата в a[5], a[6], a[7], a[8], ..., слияние пар b[n/2-1], b[n/2] и b[n/2+1], b[n/2+2] с помещением результата в a[n-3], a[n-2], a[n-1], a[n]. И т.д. На последнем шаге, например (в зависимости от значения n), производится слияние последовательностей элементов массива длиной n/2 a[1], a[2], ..., a[n/2] и a[n/2+1], a[n/2+2], ..., a[n] с помещением результата в b[1], b[2], ..., b[n].
Для случая массива, используемого в наших примерах, последовательность шагов показана в таблице 4.6.1.
Таблица 4.6.1 Пример сортировки со слиянием
|
Начальное состояние массива |
8 23 5 65 44 33 1 6 |
|
Шаг 1 |
6 8 1 23 5 33 44 65 |
|
Шаг 2 |
6 8 44 65 1 5 23 33 |
|
Шаг 3 |
1 5 6 8 23 33 44 65 |
При применении сортировки со слиянием число сравнений ключей и число пересылок оценивается как O(n?log n). Но следует учитывать, что для выполнения алгоритма для сортировки массива размера n требуется 2?n элементов памяти.
Достоинства:
- Работает даже на структурах данных последовательного доступа.
- Хорошо сочетается с подкачкой и кэшированием памяти.
- Неплохо работает в параллельном варианте: легко разбить задачи между процессорами поровну, но трудно сделать так, чтобы другие процессоры взяли на себя работу, в случае если один процессор задержится.
- Не имеет «трудных» входных данных.
- Устойчивая - сохраняет порядок равных элементов (принадлежащих одному классу эквивалентности по сравнению).
Недостатки:
- На «почти отсортированных» массивах работает столь же долго, как на хаотичных.
- Требует дополнительной памяти по размеру исходного массива.
4.7. Сравнение методов внутренней сортировки
Для рассмотренных в начале этой части простых методов сортировки существуют точные формулы, вычисление которых дает минимальное, максимальное и среднее число сравнений ключей (C) и пересылок элементов массива (M).
Таблица 4.7.1 Характеристики простых методов сортировки
|
Min |
Avg |
Max |
|
|
Прямое включение |
C = n-1 |
(n2 + n - 2)/4 |
(n2 -n)/2 - 1 |
|
Прямой выбор |
C = (n2 - n)/2 |
(n2 - n)/2 |
(n2 - n)/2 |
|
Прямой обмен |
C = (n2 - n)/2 |
(n2 - n)/2 |
(n2 - n)/2 |
Для оценок сложности усовершенствованных методов сортировки точных формул нет. Известно лишь, что для сортировки методом Шелла порядок C и M есть O(n(1.2)), а для методов Quicksort, Heapsort и сортировки со слиянием - O(n?log n). Однако результаты экспериментов показывают, что Quicksort показывает результаты в 2-3 раза лучшие, чем Heapsort (в таблице 4.7.2 приводится выборка результатов из таблицы). Видимо, по этой причине именно Quicksort обычно используется в стандартных утилитах сортировки (в частности, в утилите sort, поставляемой с операционной системой UNIX).
Таблица 4.7.2 Время работы программ сортировки
|
Упорядоченный массив |
Случайный массив |
В обратном порядке |
|
|
n = 256 |
|||
|
Heapsort |
0.20 |
0.08 |
0.18 |
|
n = 2048 |
|||
|
Heapsort |
2.32 |
0.72 |
1.98 |
4.8. Общий анализ приведенных сортировок
Приведем выводы по простым методам сортировки:
Время сортировки пропорционально квадрату размерности массива
Более точные оценки производительности простых методов сортировки показывают, что наиболее быстрой является сортировка вставками, а наиболее медленной - сортировка обменом.
Несмотря на плохое быстродействие, простые алгоритмы сортировки следует применять при малой размерности сортируемого массива.
При больших размерностях массива они обеспечивают существенный выигрыш.
Сравним простые и сложные методы сортировки по производительности: