Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Разработка эффективных алгоритмов.doc
Скачиваний:
116
Добавлен:
24.11.2019
Размер:
1.2 Mб
Скачать

3.4. Сортировка Шелла.

Этот метод был предложен Шеллом (Дональд Л. Шелл) в 1959 г. Основная идея этого алгоритма заключается в том, чтобы устранить массовый беспорядок в массиве, сравнивая элементы, далеко отстоящие друг от друга.

Необычность метода состоит в том, что он рассматривает весь список как совокупность перемешанных подсписков. На первом шаге эти подсписки представляют собой просто пары элементов. На втором шаге - в каждой группе по 4 элемента. На каждом следующем шаге число элементов в каждом подсписке увеличивается, а число подсписков соответственно уменьшается.

Сортировка подсписков выполняется путем однократного применения сортировки вставками. Но примем во внимание, что в сортировке Шелла упорядочиваются перемешанные подсписки, и обмен элементов может уменьшить не одну инверсию, а большее их число.

ShellSort (A,N)

pass  [ log 2 N] // число шагов алгоритма

while pass  1 do

// inc - величина шага между элементами подсписка

// Значение шага inc так же равно числу подсписков.

inc  2 ^ pass – 1

for start = 1 to inc do

InsertSort (A, N, start, inc)

endfor

pass  pass - 1

endwhile

end

Было доказано, что сложность этого алгоритма O(N3/2).

Для этой сортировки чрезвычайно важна последовательность шагов. Выбор последовательности шагов может оказать решающее влияние на сложность сортировки, однако попытки поиска оптимальной последовательности не привели к искомому результату.

Пример 3.3 Сортировка Шелла для списка (7, 3, 9, 4, 2, 5, 6, 1, 8).

1 шаг pass = [log 2 9] = 3, inc = 2^3-1=7. Выделим 7 подсписков. В первый подсписок включаются первый и восьмой (1+inc) элементы, во второй – второй и девятый, в третий – только 3 элемент соответственно и т.д. (7 1) (3 8) (9) (4) (2) (5) (6)

Инверсии (нарушение порядка) обнаружены в первом подсписке. Все подсписки сортируются методом вставок (поменяв местами 7 и 1 мы убираем семь инверсий в обычной сортировке). Сравнений 2, перестановок (сдвигов) 1. После сортировки список имеет вид:

1, 3, 9, 4, 2, 5, 6, 7, 8.

2 шаг. Pass=2, inc=3. Выделяем 3 подсписка: (1 4 6) (3 2 7) (9 5 8). Сортируем подсписки. Сравнений 7, перестановок 3. Итоговый список:

1, 2, 5, 4, 3, 8, 6, 7, 9

3 шаг. Pass=1, inc=1. Работаем с общим списком. Обычная сортировка методом вставок. Сравнений 13, перестановок – 5.

Общее число сравнений – 22, перестановок – 9 (против 26 и 19 соответственно в обычной сортировке вставками для этого случая).

3.5. Корневая сортировка

Все элементы списка –целые, положительные, несовпадающие числа. При корневой сортировке упорядочивание списка происходит без непосредственного сравнения ключевых значений между собой.

При этом создается набор «стопок» (пачек), а элементы распределяются по стопкам в зависимости от значений ключей. Собрав значения обратно и повторив процедуру для последовательных частей ключа, мы получаем отсортированный список.

Чтобы такая процедура работала, распределение по стопкам и последующую сборку необходимо выполнять очень аккуратно.

Рассмотрим этот алгоритм на примере.

Пример 3.4 Дан список из 16 элементов

310 213 023 130 013 222 301 032 201 111 323 002

330 102 231 120

1 проход. Делим список на стопки по значению младшего разряда.

N стопки Содержание

0 310 130 330 120

1 301 201 111 231

2 222 032 002 102

3 213 023 013 323

Объединяем стопки в единый список

310 130 330 120 301 201 111 231 222 032 002 102

213 023 013 323

2 проход. Делим список на стопки по значению следующего разряда (десятки).

0 301 201 002 102

1 310 111 213 013

2 120 222 023 323

3 130 330 231 032

Объединяем стопки в единый список

301 201 002 102 310 111 213 013 120 222 023 323

130 330 231 032

3 проход. Последнее деление по значению старшего разряда (сотни).

0 002 013 023 032

1 102 111 120 130

2 201 213 222 231

3 301 310 323 330

Окончательный вариант списка

002 013 023 032 102 111 120 130 201 213 222 231

301 310 323 330

Ниже приведен возможный вариант алгоритма для ключа, в котором используется 10 цифр (следовательно, раскладка возможна по 10 стопкам), размер ключа КeySize определяет количество разрядов в элементе списка.

RadixSort (A, N)

// А – сортируемый список

// N – размер списка

shift  1

for i=1 to КeySize do

for j=1 to N do

st_nom  (A[j]/Shift) mod 10

//процедура Append записывает элемент в соответствующую стопку

Append (st[st_nom], A[j])

endfor

// процедура Combine объединяет стопки в общий список

Combine(St, А)

shift  shift * 10

endfor

end

При вычислении значения переменной st_nom (номер стопки) из ключа вытаскивается одна цифра. При делении на shift ключевое значение сдвигается на несколько позиций вправо, а последующее применение операции mod оставляет лишь цифру единиц полученного числа.

При первом проходе с величиной сдвига 1 деление не меняет числа, а результатом операции mod служит цифра единиц ключа. При втором проходе с величиной сдвига shift=10, поэтому целочисленное деление и последующее применение операции mod дают цифру десятков.

Процедура Combine комбинирует стопки и формирует их все в один список. Этот переформированный список служит основой для следующего прохода

Анализ алгоритма.

Каждый ключ просматривается по одному разу на каждый разряд, присутствующий в самом длинном ключе. Поэтому, если в самом длинном ключе М цифр, а число ключей равно N, то сложность корневой сортировки O(MN).

Обычно длина ключа невелика по сравнению с числом ключей (например, при ключе из 6 цифр число записей может быть миллион: от 000000 до 999999).

Доказано, что длина ключа не играет роли, и алгоритм имеет линейную сложность O(N). Это очень эффективная сортировка, поэтому возникает вопрос, зачем вообще нужен какие-либо другие методы?

Ключевым препятствием в реализации корневой сортировки служит ее неэффек­тивность по памяти. В предыдущих алгоритмах дополнительная память равняется длине записи (элемента). Теперь дополнительной памяти требуется гораздо больше.

Если стопки реализовать массивами, то эти массивы должны быть чрезвычайно велики (на самом деле, величина каждого из них должна совпадать с длиной списка, поскольку у нас нет оснований предполагать, что значения ключей распределены равномерно по стопкам).

Вероятность того, что во всех стопках окажется одинаковое число записей совпадает с вероятностью того, что все записи попадут в одну стопку. И то, и другое может произойти.

Поэтому реализация на массивах приведет дополнительно к выделению 10N записей для числовых ключей, ЗЗN (если русские буквы) для символьных ключей (причем это место еще увеличивается, если в ключ могут входить произвольные символы или в буквенных ключах учитывается регистр).

Кроме того, при использовании массивов нам потребуется время на копирование записей в стопку на этапе формирования стопок и на обратное их копирование в список на этапе сборки списка (то есть, каждая запись перемещается 2М раз).

Таким образом, повышение временной эффективности достигается за счет увеличения дополнительной памяти и решение о выборе данного алгоритма должно приниматься на основе комплексной оценки.

Другой подход состоит в объединении записей в список со ссылками. В этом случае, как помещение записи в стопку, так и ее возвращение в список требует лишь изменения ссылок. Так как на ссылку уходит от 2-х до 4-х байт, дополнительная память все равно значительна – 2N или 4N байт.