一、实验目的及要求
1.在以BUF为首址的字存储区中存放有N个有符号数,现需将它们按大到小的顺序排列在BUF存储区中,试编写其程序。要求:
i N的数目自己定义,但不要超过20
ii 要写出编程的思路并画出流程图
iii 在程序中使用标志位进行程序的优化
2.请画出实验1.6:子程序结构的流程图
二、实验设备(环境)及要求
1.硬件:PC微机,计算机系统Windows
2.软件:DOS系统、EDIT.EXE、汇编MASM.EXE、连接LINK.EXE、调试DEBUG.EXE等应用程序,Windows自带记事本程序
三、实验内容与步骤
(一)冒泡排序
1.冒泡排序程序思路
从第一个数开始依次对相邻两个数进行比较,如前面的数大于后面的数则不进行操作,如果后面的数大于前面的数则将两个数字交换位置。从第一个数开始遍历,若共有N个数字,第一遍需要比较(N-1)次,后面每一遍比较的次数逐次递减,直到所有数字都按从大到小的顺序排列。在比较的过程中,如果有一遍比较时未发生数字的位置交换,则证明所有数字已经按照从大到小的顺序排列,后面无需继续比较,可以直接跳出循环结束程序。程序框图如下。
2.准备被调试程序
DATA SEGMENT
BUF DW 12,10,-4,2,-6,-3,5,0,9
N = ($-BUF)/2
FL DB ? ;设置标志FL,用来判断一趟循环中是否发生位置交换
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA,SS:STACK
START: MOV AX,DATA
MOV DS,AX
MOV CX,N ;装填计数初值
DEC CX ;实际循环次数为N-1次
MOV DX,CX
MOV FL,0
LOOP1: LEA BX,BUF
MOV CX,DX
MOV FL,0 ;每次循环前将FL置0
LOOP2: MOV AX,BUF[BX]
CMP AX,BUF[BX+2]
JGE L ;如果前面的数字大于等于后一个数字则不发生换位,直接跳转到L,若要实现无符号数的排序,可以将JGE换为JAE
XCHG AX,BUF[BX+2] ;若前一个数小于后一个数则交换两个数字
MOV BUF[BX],AX
MOV FL,1 ;如果发生了数字换位就将FL置为1
L: ADD BX,2
DEC CX
JNE LOOP2 ;CX=0跳转LOOP2
MOV CX,DX
CMP FL,0
JE OVER ;如果FL=0则未发生换位,可以直接跳出循环结束
DEC DX
LOOP LOOP1
OVER: MOV AH,4CH
INT 21H ;程序终止,返回DOS
CODE ENDS
END START
3.进入DEBUG环境调试
(1)生成obj文件和可执行文件
(2)存储区初始状态
通过-D可以看到存储的数据,依次存放12,10,-4,2,-6,-3,5,0,9
如-6补码为1111 1111 1111 1010=FFFAH,存储时低字节存入低地址,高字节存入高地址,故存储时显示FA FF
(3)-U反汇编
找到一趟循环结束的跳转指令IP=0042
(4)运行至断点
上图为一趟循环结束,最小值移动到最右边,即图中的FAFF(-6),后面的1为标志FL
(5)循环至程序终止
循环进行到倒数第二趟时所有数都已经按从大到小的顺序排列,未发生位置交换,故FL=0,跳出循环结束程序
4.对程序的分析
排序过程中需要遍历N-1次,每次遍历通过前后两个数字交换的方式将最小数移动到最右边,直至所有数字按照从大到小的顺序依次排列。冒泡排序的比较次数 = (n - 1) + (n - 2) + ... + 2 + 1,即:n * (n - 1) / 2,所以冒泡排序的时间复杂度为O(n2)。
(二)选择排序
1.选择排序程序思路
从第一个数开始依次向后比较,找到最大的数存起来,在把这个数与最左端的数交换位置,然后对剩余未排序的数字重复此操作,每次都将未排序的数放到这些数中的最左端,最后可以实现数字从大到小排序。
2.准备被调试程序
DATA SEGMENT
BUF DW 2,10,-4,-2,-6,-3,5,0,9
N = ($-BUF)/2
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE,DS:DATA
START:
MOV AX,DATA
MOV DS,AX ;导入数据段数据
MOV CX,N ;装填计数初值
DEC CX ;实际循环次数为N-1次
MOV BX,OFFSET BUF
LOOP2:
MOV SI,BX ;分别存入SI,DI
MOV DI,BX
PUSH CX
MOV AX,[BX] ;将初值存入AX
LOOP1: ;内层循环找出每一趟的最大值
ADD SI,2
CMP AX,[SI]
JGE NEXT ;若(AX)>=[SI],则不进行操作,若要实现无符号数的排序,可以将JGE换为JAE
MOV AX,[SI] ;否则将较大的值先放入AX
MOV DI,SI
NEXT:
LOOP LOOP1
XCHG AX,[BX] ;一趟比较过后,将AX中存放的最大值与最左端未被排序的数交换
XCHG AX,[DI]
ADD BX,2
POP CX
LOOP LOOP2
EXIT:
MOV AH,4CH
INT 21H
CODE ENDS
END START
3.进入DEBUG环境调试
(1)生成obj文件和可执行文件
(2)存储区初始状态
通过-D可以看到存储的数据,依次存放2,10,-4,-2,-6,-3,5,0,9
(3)-U反汇编
找到一趟比较结束的跳转指令IP=0028
(4)运行至断点
上图为一趟比较结束后,找到的最大值被放到最左端,即图中的0A 00(10)
上图为第二趟比较结束后,在后8个数字中找到的最大值09 00(9),被放到第二趟比较中的最左端,即第二个位置
后面继续循环,重复此操作
(5)循环至程序终止
外层循环进行到CX=0时循环终止,结束程序,从上图可以看出9和数字都已经按照从大到小的顺序排列,顺序依次为10,9,5,2,0,-2,-3,-4,-6
4.对程序的分析
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
常见的选择排序可以分为直接选择排序、树形选择排序以及堆排序,这里使用的是直接选择排序。选择排序的交换操作介于0和(n - 1)次之间。选择排序的比较操作为n (n - 1)/ 2次之间。选择排序的赋值操作介于0和3 (n - 1)次之间。直接选择排序的时间复杂度为O(n2)。
四、实验结果
1.冒泡排序
对12,10,-4,2,-6,-3,5,0,9进行排序,结果如上图
2.选择排序
对2,10,-4,-2,-6,-3,5,0,9进行排序,结果如上图
0A00(10) >0900(9) >0500(5) >0200(2) >0000(0) >FEFF(-2) >FDFF(-3) >FCFF(-4) >FAFF(-6)
3.实验1.6程序框图
见下图
五、分析与讨论
编写程序之前首先要有一个大概的思路,然后通过画程序框图的方式将这个思路具体化,这里要考虑到寄存器的选用,哪个用作计数器,哪个用作指针。在刚开始绘制程序框图的时候我忽略了一个问题,就是执行loop循环的时候会先将(CX)-1,再判断(CX)是否等于0,这是一个需要注意的地方。
第二步就是根据程序框图编写程序。首先可以直接套用汇编程序的框架,然后将需要用到的数据和变量填在数据段,代码段的编写根据程序框图完成。在这一步我遇到的问题是寄存器的选用,如果将数据定义为一个字,就应该选用AX等寄存器,而不能使用AL,在指针移位时也要使用+2的指令,而不能使用INC指令。在对照程序框图进行编成的过程中还要注意上面提到的LOOP的问题,如果跳转使用LOOP指令,则前面无需进行DEC CX。
在对程序进行静态检查后,可以进行上机调试。首先masm和link生成obj和exe文件,在这个过程中可以检查到一些错误,如下图所示。若masm报错,需要对程序进行检查修改,如果link出现没有堆栈段,也可以正常运行。
在调试的过程中需要配合使用-u,-g和-d,-u可以找到我们想要跳转指令的偏移地址,-g+IP可以直接直接跳转到该条指令,-d可以随时查看数据的存储状态。注意在使用-d时要在后面加0000,才能从0000单元开始显示,否则找不到排序数字存储的区域,出现如下图情况。
通过编写和修改两种排序程序,我掌握了编写汇编语言程序的基本方法,并且能够熟练使用DOS操作系统的各项指令,通过debug对程序进行动态调试并修改错误。对比两种排序方式,可以发现选择排序的交换次数远少于冒泡排序,由于交换所需CPU时间比比较所需的CPU时间多,所以在数字个数N较小时,选择排序比冒泡排序快。