- •СТРУКТУРЫ ДАННЫХ И АЛГОРИТМЫ
- •Глава 1 Линейные списки
- •Понятие структуры данных
- •Тип указатель
- •Линейный список
- •Линейный однонаправленный (односвязный) список
- •Линейный двунаправленный (двусвязный) список
- •Практическое задание «Линейные списки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 2 Структуры данных стек и очередь
- •Понятие структуры данных стек и очередь
- •Структура данных стек
- •Структура данных очередь
- •Пример решения задачи с использованием стека
- •Практическое задание «Стеки и очереди»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 3 Древовидные структуры данных
- •Понятие структуры данных дерево
- •Представление структуры бинарное дерево в памяти
- •Обход дерева
- •Основные операции над деревом
- •Ввод дерева
- •Практическое задание «Бинарные деревья»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 4 Структура данных таблица
- •Понятие структуры данных таблица
- •Представление таблицы в памяти
- •Организация хеш-таблиц
- •Выбор функции расстановки
- •Разрешение коллизии
- •Операции над таблицами
- •Практическое задание «Управление таблицами»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Глава 5 Алгоритмы упорядочения данных
- •Понятие операции упорядочения
- •Алгоритмы упорядочения
- •Сортировка слиянием
- •Основные алгоритмы сортировки посредством сравнений
- •Улучшенные алгоритмы сортировки сравнением
- •Практическое задание «Алгоритмы сортировки»
- •Задачи для самостоятельной работы
- •Контрольные вопросы
- •Библиографический список
}
j++;
}
Если исходное множество уже упорядочено, то число просмотров равно 1, иначе просмотры продолжаются до тех пор, когда на очередном просмотре не будет обнаружено ни одной инверсии.
Улучшенные алгоритмы сортировки сравнением
В основе улучшенных алгоритмов сортировки лежит идея внесения большего порядка во время одного просмотра данных. Оценка сложности улучшенных алгоритмов является не тривиальной задачей, сложность улучшенных алгоритмов сортировки выбором и обменом не менее n·log(n), сложность улучшенного алгоритма сортировки включением (метод Шелла) порядка n1.2.
1) Сортировка методом Шелла
Основным недостатком сортировки прямым включением является то, что каждый элемент перемещается за один раз только на 1 позицию. Например, если m–й элемент надо переместить в 1-ю позицию, то надо переместить на одну позицию m-1 предшествующих элементов. Каждое перемещение уничтожает только одну инверсию. В методе Шелла исходное множество разбивается на подмножества, содержащие элементы, которые отстоят друг от друга на некоторый шаг h (h-серии) . Элементы внутри каждого подмножества упорядочиваются в соответствии с алгоритмом прямого включения. Таким образом, на одном просмотре устраняется более чем одна инверсия. Просмотры продолжаются с уменьшающимся шагом h, и на последнем просмотре шаг h должен быть равен 1. Выбор значений шага разбиения на последовательных просмотрах имеет существенное значение для скорости сортировки. Для больших по размеру множеств рекомендуются следующие закономерности:
а) hi = 3hi + 1 + 1 (если нумеровать от первого применяемого шага), получаемая последовательность: …364, 121, 40, 13, 4, 1;
б) hi = 2hi + 1 + 1 (если нумеровать от первого применяемого шага), получаемая последовательность: …127, 63, 31, 15, 7, 3, 1.
113
Эти последовательности шагов обеспечивают включение на следующем просмотре в одну h-серию таких элементов, которые на предыдущем просмотре были в разных h-сериях. На первом просмотре рекомендуется выбирать шаг h достаточно большим, приблизительно равным [n / 9].
Алгоритм метода Шелла:
l = 0; h[l] = 1;
while (h[l] <= n / 9)
{
l= l + 1;
h[l] = 3 * h[l - 1] + 1;
}
for (m = l; m>= 0; m--)
for (k = 0; k < h[m]; k++)
{
i = h[m] + k;
while (i <= T.n - 1)
{
x= T.elem[i]; j = i – h[m];
while (j>= 0 && ключ(x) > ключ(T.elem[j]))
{
T.elem[j + h[m]] = T.elem[j]; j = j - h[m];
}
T.elem [j + h[m]] = x; i = i + h[m];
}
}
2) Пирамидальная сортировка
В этом методе исходное множество элементов таблицы рассматривается как бинарное дерево, вершины которого располагаются в мас-
114
сиве следующим образом: если k – индекс вершины дерева, то ее потомки находятся в элементах массива с индексами 2k + 1 и 2(k + 1); корень дерева находится в элементе с индексом 0. Двоичное дерево называется пирамидой (максимизирующим деревом или минимизирующим деревом), если в каждой его вершине находится элемент, ключ которого больше или равен (меньше или равен) ключам его непосредственных потомков. Таким образом, элемент с максимальным значением ключа находится в корне дерева. Этот элемент в алгоритме сортировки выбором и является нужным, его обмениваем с элементом, занимающим не свое место в упорядоченной части множества – первый раз с последним элементом множества, затем с предпоследним и т. д. После обмена дерево перестает быть пирамидой. Для того чтобы восстановить пирамиду достаточно просмотреть не все элементы, а только то поддерево, в котором обнаружен очередной нужный (максимальный) элемент.
Таким образом, пирамидальная сортировка состоит из двух частей:
–построение пирамиды из исходного множества;
–собственно сортировка выбором, при которой приходится восстанавливать пирамиду.
Алгоритм пирамидальной сортировки:
Пирамида(table *a, int i, int j)
{
max = i; do
{
t = max;
lt = 2 * t + 1; rt = 2 * (t + 1); if (lt <= j)
if (ключ(a->elemlt) > ключ(a->elemt)) max = lt;
if (rt <= j)
if(ключ(a->elemrt) > ключ(a->elemmax)) max = rt;
x = a->elemt;
115
a->elemt = a->elemmax; a->elemmax = x;
} while (t != max);
}
for (i = (T.n - 1) / 2; i>=0; i--)
Пирамида(&T, i, T.n - 1); for(i = T.n - 1; i > 0; i--)
{
x =T.elem0; T.elem0 = T.elemi; T.elemi = x;
Пирамида(&T, 0, i - 1);
}
3) Быстрая сортировка
Для повышения эффективности алгоритма выполняются обмены не рядом расположенных элементов, а удаленных друг от друга на большое расстояние. Неким образом выбирается элемент x, по отношению к которому упорядочиваемое множество элементов разбивается на две части: слева от x будут элементы меньшие x, а справа от x все элементы большие x. Этот процесс выбора делящего элемента и разделения множества по отношению к выбранному элементу применяется к каждой из полученных частей. Так продолжается пока очередная часть не окажется тривиально короткой, состоящей из одного элемента, она считается упорядоченной. Алгоритм завершается, когда каждая из частей будет тривиально короткой.
Проблемы быстрой сортировки: 1) Выбор делящего элемента
Существует несколько подходов к выбору делящего элемента, но ни один из них не гарантирует, что не произойдет так называемый «катастрофический» случай для быстрой сортировки, когда множество делится на две не равные части: одна содержит всего один элемент, а вторая все остальные элементы. Если исходное множество является случайным и ключи в нем равномерно распределены, то можно:
116
-выбирать элемент случайным образом (его индекс);
-брать делящим элементом серединный элемент – элемент с индексом (i + j)/2 или (i + (i + j) / 2 + j) / 3, где i – индекс начала упорядочиваемой части, j – индекс последнего элемента упорядочиваемой части.
2) Что делать с элементами, имеющими равные ключи? Исключение обмена элементов с равными ключами приводит к
уменьшению действий при обмене, но также к усложнению условия завершения процесса разделения множества на две части. Поэтому лишние обмены (обмены элементов с равными ключами) выполняются, и это даже способствует разделению множества почти пополам.
3) Когда остановить процесс разделения множества?
Теоретически разделение можно закончить, когда все части содержат по одному элементу, но при множествах небольшого размера эффективность алгоритма снижается. Рекомендуется завершать процесс разделения, когда размер части будет меньше или равен некоторому пороговому значению. Затем выполнить последний проход сортировки методом прямого включения (среди простых методов он в среднем наиболее эффективен). В качестве порогового значения для размера части рекомендуется 9 – 15 элементов.
Рекурсивная реализация алгоритма быстрой сортировки:
Delete (table *a, int L, int R, int *ri, int *rj)
{
c = a->elem(L + R) / 2; i = L;
j = R;
while (i <= j)
{
while (ключ(a->elemi) < c) i++; while (ключ(a->elemj) > c) j--; if (i <= j)
{
x = a->elemi; a->elemi = a->elemj; a->elemj = x;
117
