Реализация сортировки слияниями


include masm32rt.inc
.data
  n dd ?; число элементов массива
  a dd ?; адрес сортируемого массива 
  p dd ?; адрес буферного массива

Массивы расположим в динамической памяти для ускорения компиляции (почему-то компиляция исходного текста программы, содержащей большие статические массивы, может затягиваться очень надолго).

Нам понадобится несколько процедур. Их я предлагаю вам написать самостоятельно.

1) Процедура, возвращающая в ЕАХ случайное число (подойдёт randval, приведенная выше)

2) Процедура заполнения массива а n 32-разрядными числами

3) Процедура вывода массива а на экран.

Перейдём теперь к процедуре сортировки. В ней я объединил рекурсию со слиянием. В своей реализации я полностью отказался от использования индексов, заметив, что их можно заменить адресами! Такой подход упрощает вычисления, позволяя избавиться от умножений или сдвигов для вычисления адресов по индексам. В регистры EAX и EBX передаются адреса L-го и R-го элементов.



.code ; ... sort proc uses eax esi mov esi, ebx ; в регистре ESI будем хранить адрес M-го элемента массива. add esi, eax ; вычисляем сумму адресов L - го и R - го элементов. test esi, 4 ; средний элемент нужно взять "с перебором", jz @skip ; добиваемся этого вот таким хитрым способом, заметив, что add esi, 4 ; "лишняя" единичка имеет значение только в том случае, если @skip: ; сумма адресов при делении на 8 даёт в остатке 4. shr esi, 1 ; делим сумму пополам. .if eax < esi ; если L < M... push ebx ; не забываем поместить значение ЕВХ в стек... mov ebx, esi ; ... так как уже в этой строке его значение меняется. sub ebx, 4 ; вычитаем 4 и вызываем процедуру рекурсивно для "индексов" call sort ; от L до M-1, фактически в роли индексов у нас адреса. pop ebx ; восстанавливаем значение EBX. .endif .if esi < ebx ; аналогично вызываем рекурсию для адресов от a[M] до a[R] push eax mov eax, esi call sort pop eax .endif ; Следующий шаг - слияние. push eax ; кладём в стек адрес L-го элемента, он понадобится далее. mov edx, a ; следующие 5 строк - вычисление адреса p[L]-го элемента: ; сейчас EDX = [а[0]]. mov edi, eax ; теперь в EDI помещаем адрес a[L]. sub edi, edx ; находим смещение a[L]-го элемента относительно [a[0]]. mov edx, p ; в EDX помещаем адрес p[0]. add edi, edx ; цель достигнута - в EDI лежит адрес p[L], полученный ; добавлением к p[0] смещения на L элементов. push edi ; так как адрес p[L] нам понадобится в следующем цикле, ; нужно его запомнить. mov ecx, ebx ; я хочу занести в ЕСХ число итераций для следующих циклов. sub ecx, eax ; для этого я вычислю разность адресов [a[R]] и [a[L]]... shr ecx, 2 ; ... затем разделю её на 4... inc ecx ; ... и увеличу на 1. push ecx ; запомним это значение, оно потребуется в следующем цикле. mov edx, esi ; в EDX будет хранится адрес a[j]-го элемента массива. .while ecx!=0 ; повторяем цикл ЕСХ раз. push ebx ; нам не хватит регистров... push ecx ; ... поэтому жертвуем EBX и ECX, помещая их значения в стек. ; обрабатываем сложный условный переход: cmp edx, ebx ; здесь сравниваем адреса a[j] и a[R] mov ebx, [eax] ; помня, что инструкция mov не влияет на регистр флагов, ; условный переход можем осуществить ниже, а пока ; поместим в EBX значение a[i]-го элемента, которое ; понадобится нам далее. mov ecx, [edx] ; в регистр ЕСХ помещаем элемент a[j]. ja @then ; если адрес [a[j]] был больше адреса [a[R]], ; переходим на метку @then. cmp eax, esi ; сравниваем адреса [a[i]] и [a[M]]... jae @else ; ... и в случае, если [a[i]] больше [a[M]], ; переходим на метку @else. cmp ebx, ecx ; здесь сравниваем сами значения элементов a[i] и a[j]. jae @else ; идём на @else, если i-й элемент больше или равен j-му. @then: mov [edi], ebx ; инструкция, соответствующая команде "p[k]:=a[i]" add eax, 4 ; перемещаем указатель на следующий элемент jmp @endif @else: mov [edi], ecx ; "p[k]:=a[j]" add edx, 4 ; аналогично - перемещаем указатель на следующий ; после j-го элемент @endif: pop ecx ; восстанавливаем регистры ЕСХ... pop ebx ; ... и ЕВХ. add edi, 4 ; перемещаем указатель на следующий после p[k]-го элемент. dec ecx ; уменьшаем счётчик цикла. .endw pop ecx ; вспоминаем значение ЕСХ... pop esi ; ... адрес [p(L)]... pop edi ; а также адрес [a[L]]. rep movsd ; последний цикл ret sort endp

Процедура main не представляет особого интереса и может быть написана самостоятельно. Сначала выполняется заполнение массива и вывод его на экран, затем вызывается сортировка, при этом в регистр ЕАХ должен быть предварительно записан адрес массива а, а в ЕВХ - адрес его последнего элемента. Массивы a и p можно создать в динамической памяти с помощью Windows API-функции HeapAlloc, пример использования которой был приведён в предыдущем алгоритме сортировки, правда теперь придётся выделить не 12 байт памяти, а n*4, словно массив - это большая-пребольшая переменная. Дескриптор кучи можно получить функцией GetProcessHeap.




© Бараев Николай Михайлович