Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Структуры данных и алгоритмы. Учебное пособие.pdf
Скачиваний:
0
Добавлен:
15.08.2026
Размер:
1 Мб
Скачать

}

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

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