题目描述&代码
有一个首地址为A的N字数组,编写程序采用冒泡排序使该数组中的数按照从大到小的次序整序。
数据存储在A的数组中(即内存中),我们需要利用冒泡排序实现从大到小排序。
;description
data SEGMENT USE16a dw 8,16,41,22,50n equ ($-a)/2
data ENDS
;description
stack SEGMENT USE16dw 32 dup(?)
stack ENDS
;description
code SEGMENT USE16ASSUME CS:CODE, DS:DATA, SS:STACKstart:mov ax,datamov ds,ax;存储n-1到cxmov cx,ndec cxloop2:;存储cx到dxmov dx,cxshl dx,1mov si,0loop1:mov ax,a[si]CMP word ptr ax,a[si+2]ja next;bx相当于是中间变量,交换mov bx,axmov ax,a[si+2]mov a[si+2],bxmov a[si],axnext: add si,2cmp si,dxjnz loop1loop loop2mov ah,4chint 21h
code ENDS
end start
冒泡排序
对于一个数组,我们的冒泡排序是这样的:
比较相邻两项,将小的放在后面,重复n-1次,就能得到有序序列。
这是一趟:
很明显,我们这样就能将最小的元素排在我们想要的位置。
然后进行第二轮,同理我们可以将第二小的元素排在合适的位置。
然后是第三轮、第四轮……,直到第n-1轮整体有序。
每一次最小的元素被选出来,就像是交换到了水面上,所以叫做冒泡排序。
这里再做一个优化,我们可以看出来,每一次需要排序的元素个数都会少一个。
实现部分我是用cx寄存器存储循环的n-1次,每一轮减一,刚好和需要排序的个数相同。
实现
数据准备
首先,我们已知数据存储在a数组中,这里我们计算元素个数的方式是采用了地址计数器"$",他的作用是保存当前正在汇编的指令的偏移地址。
还有伪指令equ,有一点像等号,其实就是一个赋值操作,不过只能做一次。
在赋值的时候,地址计数器刚好将数组存入内存,并且地址计数器保存的是指令的第一个字节位置,
图示:(这里的数据为十六进制)
这样我们用地址计数器减去a(这里是一个地址)就能得到该数组包含的字节个数,然后除以2就是字的个数了。
双重循环
这里的循环很明显是两层,如果都是loop指令就需要进行将cx寄存器压栈,我们采用的是cx和dx分别作为外层和内层的计数器。
在外层循环中,我们需要将cx的值递减(loop指令完成),同时需要给dx寄存器赋值,原因在上面说过了,我们想要优化一下排序次数。
因为数组的数据类型为dw,所以si下标一次加2,我们需要将dx乘二(左移一位)从而和si比较。
在内层循环中,需要比较a[si]和a[si+2],但是因为两个不能都是内存寻址,这里我们先将a[si]放在ax中,然后和a[si+2]比较,注意在换的过程中是需要中间变量的,而且是修改数组的值。(我个憨憨最开始就只修改了ax寄存器的值)
在每一次交换之后,将si寄存器内容加2,和dx的值比较一下,如果不同说明没有达到最后,否则停止进入外层循环。
这里声明一下,内存中的数据都是以字节为单位进行操作的,所以我们将si加2(dw类型)。
因为懒得写输出结果,所以我就直接debug查看内存了:
先使用g运行程序,然后找到存储data段的地址,因为我们的数组a是最开始的,所以偏移地址为0000。
因为地址从左到右是从低到高,所以3200表示的其实是0032,也就是十进制的50,以此类推。
对debug感兴趣的可以看这篇博客。