Реализация сортировки Шелла
include e:\masm32\include\masm32rt.inc
.data
n dd ? ; число элементов сортируемого массива.
a dd ? ; адрес массива в памяти. Как и в прошлый раз, массив будет
; храниться в динамической памяти.
.code
; Здесь нам снова потребуются традиционные для генерации массива процедуры:
; randval, а также заполнение и вывод массива на экран.
; Без долгих прелюдий привожу текст процедуры сортировки:
sort proc ; начнём с заполнения "массива" шагов. "А где
; же объявлен массив?", - спросите вы. На самом деле
; это будет не массив, я решил помещать шаги прямо в
; стек по двум причинам:
; 1) Занесение элементов в стек и извлечение их
; оттуда осуществляется очень просто - командами push
; и pop.
; 2) Заметим, что вычисление шагов идёт по
; возрастанию, а требоваться они нам будут наоборот –
; в порядке убывания. Стек здесь идеально подходит.
xor ecx, ecx ; в регистре ЕСХ будем хранить индекс очередного
; шага, хотя он нам не будет важен, важна будет
; только его чётность, т. к. формула Седжвика сложная
; и фактически состоит из двух формул - для чётных и
; нечётных элементов последовательности.
mov eax, 1 ; в регистрах EAX и EDX будем хранить степени двойки,
; участвующие в формулах.
mov edx, eax ; присваиваем им начальные значения.
.repeat ; цикл, повторяющийся до тех пор, пока утроенная
; величина очередного шага не превысит числа n.
mov ebx, eax ; чтобы не потерять значения степеней двоек при
; дальнейших вычислениях, копируем их в регистры EBX
mov esi, edx ; и ESI.
test ecx, 1 ; проверяем индекс очередного элемента на чётность.
jz @else ; в случае чётности переходим на метку @else.
shl ebx, 3 ; пользуемся формулами Седжвика. Умножаем 2^i на 8.
lea esi, [esi+esi*2]; хитрый способ умножения на 3...
add esi, esi ; ... и ещё на 2, т. е. на 6 в итоге.
jmp @endif
@else:
lea ebx, [ebx+ebx*8]; вновь хитрое умножение, на этот раз на 9.
lea esi, [esi+esi*8]; и здесь оно же.
add edx, edx ; берём следующую степень двойки при вычитаемом.
@endif:
sub ebx, esi ; вычитаем из первого произведения второе...
inc ebx ; ... и увеличиваем выражение на 1
shl ebx, 2 ; помня, что элементы сортируемого массива занимают в
; памяти по 4 байта каждый, можем умножить значение
; полученного шага на 4, чтобы потом "перепрыгивать"
; на необходимое число байт.
push ebx ; полученный шаг помещаем в стек.
add eax, eax ; берём следующую степень двойки при уменьшаемом.
lea ebx, [ebx+ebx*2]; полученное значение шага умножим на 3, чтобы
mov esi, n ; проверить, не превышает ли оно значение n*4.
shl esi, 2
inc ecx ; берём следующий индекс.
.until ebx>=esi ; переходим в начало цикла, если нужны ещё шаги.
; Значения шагов находятся в стеке, можем приступать к сортировке.
mov eax, a ; нам понадобится адрес последнего элемента
; сортируемого массива.
mov ebx, n ; вычислим его незатейливым и естественным способом.
dec ebx ; не забываем уменьшить на 1 иначе возможно обращение
; не к той памяти.
shl ebx, 2 ; не забываем также умножить на 4.
add ebx, eax ; искомый адрес теперь лежит в EBX.
.repeat ; проходим по значениям шагов, находящихся в стеке.
pop eax ; извлекаем значение очередного шага в байтах.
mov edi, a ; в регистр EDI кладём адрес массива а...
mov ecx, edi ; ... и запоминаем его значение в регистре ЕСХ, оно
; понадобится далее при сравнении.
add edi, eax ; начинаем с элемента, равного очередному шагу...
.while edi <= ebx ; ... и идём до последнего элемента.
push edi ; придётся запомнить значение EDI в стеке, так как
; ниже оно может испортиться.
mov edx, [edi] ; регистр EDX используем как буфер.
sub edi, eax ; находим разность адресов текущего элемента и
; элемента, стоящего на k шагов левее, где k –
; текущее значение шага.
@while:
cmp edi, ecx ; если EDI ушёл далеко влево за пределы массива...
jb @endwhile ; ... прерываем цикл.
mov esi, [edi]; EDI находится внутри массива, значит нужно сравнить
; значение элемента по адресу EDI с тем, который
; находится в буфере, т. е. в регистре EDX.
cmp edx, esi ; производим это сравнение...
jae @endwhile ; если буфер оказался меньше, выходим из цикла.
mov [edi+eax], esi; помещаем этот элемент на k шагов правее.
sub edi, eax; переходим на k шагов влево...
jmp @while ; ... и повторяем цикл.
@endwhile:
mov [edi+eax], edx; ставим на место элемент, лежащий в буфере
pop edi ; восстанавливаем старое значение EDI
add edi, 4 ; повторяем внешний цикл для следующего элемента
.endw
.until eax==4 ; самый внешний цикл заканчиваем, когда значение шага
; равно 1 (или 4 в байтах)
ret
sort endp