题目描述&代码
有一个首地址为A的N字数组,编写程序采用冒泡排序使该数组中的数按照从大到小的次序整序。
数据存储在A的数组中(即内存中),我们需要利用冒泡排序实现从大到小排序。
;description
data SEGMENT USE16
a dw 8,16,41,22,50
n equ ($-a)/2
data ENDS
;description
stack SEGMENT USE16
dw 32 dup(?)
stack ENDS
;description
code SEGMENT USE16
ASSUME CS:CODE, DS:DATA, SS:STACK
start:
mov ax,data
mov ds,ax
;存储n-1到cx
mov cx,n
dec cx
loop2:
;存储cx到dx
mov dx,cx
shl dx,1
mov si,0
loop1:
mov ax,a[si]
CMP word ptr ax,a[si+2]
ja next
;bx相当于是中间变量,交换
mov bx,ax
mov ax,a[si+2]
mov a[si+2],bx
mov a[si],ax
next:
add si,2
cmp si,dx
jnz loop1
loop loop2
mov ah,4ch
int 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感兴趣的可以看这篇博客。
本文详细讲解如何使用汇编语言实现冒泡排序,通过数据准备和双重循环来优化排序过程,减少比较次数。文章介绍了如何利用地址计数器计算元素个数,并在内外层循环中有效地进行数值比较和交换,最终通过debug查看内存验证排序结果。
1万+

被折叠的 条评论
为什么被折叠?



