使用汇编语言实现快速排序 |除法不溢出

万丈高楼平地起

运行截图

在这里插入图片描述

使用的语言环境

汇编语言为intel 8086机上的16位语言。
采取dosbox模拟实地址模式的8086机。
汇编器为MASM-v6.11
以VScode作为编辑器。

全部代码

assume cs:code, ds:data, ss:stack

data segment
    buffer dw 16 dup(0)
    CRLF db 13, 10, '$'
    tip0 db 'Please input the length of numbers you want to sort(maximum is 16):', 13, 10, '$'
    tip1 db 'Please input the number you want to sort:(each number end with a non-digital number):', 13, 10,'$'
    tip2 db 'Below is the sorted numbers:', 13, 10, '$'
data ends

stack segment
    dw 32 dup(0)
stack ends

code segment
    start:
    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, 40h

    

    mov dx, offset tip0
    mov ah, 9
    int 21h
    
    call input
    mov cx, ax
    ;12a    -> c
    ;23b    -> 17
    ;9c     -> 9
    ;45d    -> 2d
    ;8e     -> 8
    mov dx, offset tip1
    mov ah, 9
    int 21h

    mov si, offset buffer
    call inputSomeData

    ;mov di, 2
    ;call partition
    call quickSort

    mov dx, offset tip2
    mov ah, 9
    int 21h

    call outputSomeData

    mov ah, 4ch
    int 21h

    quickSort:
    ;select the 0-th 
    ;select base number less put in the left bigger in the right
    ;input : cx = length of nums; ds:si = the first address of array
    ;return : ds:si = the first address of sorted array
    ;--calcuate first and last 
    push si
    push di
    push cx
    ;-------------
    shl cx, 1; word byte
    add cx, si
    mov di, cx
    sub di, 2
    call quickSortLeftRight
    ;-------------
    pop cx
    pop di
    pop si
    ret


    quickSortLeftRight:
    ;input : ds:si = the first address of array; ds:di = the last address of array;
    ;return : ds:si = the first address of sorted array
    ;
    push si
    push di
    push bp
    ;----------------
    ;---if si >= di then quit
    cmp si, di
    jnb endQuickSortLeftRight
    ;---------
    call partition
    ;bp is the bias place
    ;
    ;-----------
    push si
    push di
    ;si - bp-2------------
    sub bp, 2
    mov di, bp
    call quickSortLeftRight
    pop di
    ;bp+2 - di------------
    add bp, 4
    mov si, bp
    call quickSortLeftRight
    pop si
    ;----------------
    endQuickSortLeftRight:
    pop bp
    pop di
    pop si
    ret

    partition:
    ;
    ;input : ds:si = the first address of array; ds:di = the last address of array
    ;return : bp = the address of bias
    push si
    push di
    push cx
    push bx
    push ax
    ;----------
    mov bx, word ptr ds:[si]
    ;
    mov bp, si; record the address of bias number
    add si, 2;
    ;
    ;if only have one element
    cmp bp, di
    jz endPartition
    ;----------
    LeftLessRight:
    findLeft:
    mov ax, word ptr ds:[si]
    cmp ax, bx
    jnb endFindLeft
    add si, 2
    ;-----------if out of size-
    cmp si, di
    ja endPartition
    ;-------------------------
    jmp findLeft
    endFindLeft:nop

    findRight:
    mov ax, word ptr ds:[di]
    cmp ax, bx
    jna endFindRight
    sub di, 2
    ;------------------if out of size
    cmp si, di
    ja endPartition
    ;-------------------------------
    jmp findRight
    endFindRight:nop


    ;--------avoid swap, si > di
    cmp si, di
    jnb endPartition
    ;swap two number
    mov ax, word ptr ds:[si]
    mov cx, word ptr ds:[di]
    mov word ptr ds:[di], ax
    mov word ptr ds:[si], cx
    ;
    ;add si, 2
    ;sub di, 2
    cmp si, di
    jna LeftLessRight
    ;-----------
    endPartition:
    ;-------swap the bais number
    mov ax, word ptr ds:[di]
    mov word ptr ds:[di], bx
    mov word ptr ds:[bp], ax
    ;----------
    mov bp, di
    ;----------
    pop ax
    pop bx
    pop cx
    pop di
    pop si
    ret


    inputSomeData:
    ;function : read cx number from keyborad, end with nondigital charter
    ;input : cx = number of input, ds:si = the first address of  store number
    ;return : void
    push ax
    push cx
    push dx
    push si
    ;-----------------
    lea dx, CRLF
    inputSomeDataLoop:
    call input
    mov word ptr ds:[si], ax

    mov ah, 9
    int 21h 

    add si, 2
    loop inputSomeDataLoop
    ;----------------
    pop si
    pop dx
    pop cx
    pop ax
    ret
    
    outputSomeData:
    push cx
    push dx
    ;-----------------
    lea dx, CRLF
    outputSomeDataLoop:
    mov ax, word ptr ds:[si]
    call output

    mov ah, 9
    int 21h 

    add si, 2
    loop outputSomeDataLoop
    ;------------------
    pop dx
    pop cx
    ret

    input:
    ;function : read number from keyborad until input charter isn't digital charter
    ;input : void
    ;output : ax <- input number
    ;
    ;压栈
    push dx
    push cx
 	push bx
    ;读取字符 放
    mov cl, 10
    mov bx, 0
    read:
    
    mov ah, 1
    int 21h
    
    cmp al, '0'
    jb quit;如果dl小于0
    cmp al, '9'
    jg quit;如果比9大
    
    mov dl, al
    mov ax, bx
    mul cl
    sub dl,'0'
    mov dh, 0
    add ax, dx
    mov bx, ax
    
    jmp read
    

    quit:
    ;输出结果
    mov ax, bx
    ;出栈
    pop bx
    pop cx
    pop dx
    
    ret 
     
    output:
    ;function : output ax as decimal based number
    ;input : ax
    ;return : void
	;压栈
	push dx
	push cx
	push bx
    push ax
    ;------------
    mov bx, 0

    mov cx, 10
    mov dx, 0
    div cx
    divLoop:
    push dx
    mov dx, 0
    div cx
    inc bx
    cmp dx, 0
    jnz divLoop

    outputLoop:
    pop dx
    mov dh, 0
    add dl, 30h;ascii
    ;char
    mov ah, 2
    int 21h
    ;
    dec bx
    cmp bx, 0
    jnz outputLoop
	;------------
	;出栈
    pop ax
	pop bx
	pop cx
	pop dx 
	ret

code ends

end start

整体思路

输出提示

获得将要输入的数据个数 (过程实现)

输出提示

将用户给定的数据放入内存 (过程实现)

进行快速排序 (过程实现)

输出提示

输出结果(过程实现)

返还控制权

    mov ax, data
    mov ds, ax
    mov ax, stack
    mov ss, ax
    mov sp, 40h

    
    mov dx, offset tip0
    mov ah, 9
    int 21h
    
    call input
    mov cx, ax
    ;12a    -> c
    ;23b    -> 17
    ;9c     -> 9
    ;45d    -> 2d
    ;8e     -> 8
    mov dx, offset tip1
    mov ah, 9
    int 21h

    mov si, offset buffer
    call inputSomeData

    ;mov di, 2
    ;call partition
    call quickSort

    mov dx, offset tip2
    mov ah, 9
    int 21h

    call outputSomeData

    mov ah, 4ch
    int 21h

input实现

规定接口

input 即从输入中不断读取数字字符,直到用户输入非数字字符。
最后返回用户输入的数据

输入123b
返回123

实现思路

从输入中读取数据,有两种方式。
一种是调用OS的中断,2号函数读取字符放到al中。
另外一种是利用BIOS的中断,从键盘的输入缓冲区读取数据。(int 9, 60端口读取数据)
此处为了简便,采取OS的中断。

接下来需要考虑的就是如何计算出正确的数值?

由于每次都需要用ax去触发OS的中断,所以把中间的结果放在ax会很麻烦,需要不断注意是否修改了数据,导致了错误。

所以在这里采取bx作为中间结果的存储。而将ax用于最后的结果返回。

但是除了之前的结果,还需要去计算当前的结果,由于mul运算只能用ax去做。

所以还需要一个寄存器用于保存之前ax的值,这里采用dx。

mul运算还需要一个操作数,只剩下CX了,所以给cl赋值10。

然后每次触发中断,判断al是否是数字字符,是就进行运算,不然的话就返回当前的结果。

实现代码如下:

  input:
    ;function : read number from keyborad until input charter isn't digital charter
    ;input : void
    ;return  : ax <- input number
    ;
    ;压栈
    push dx
    push cx
 	push bx
    ;读取字符 放
    mov cl, 10
    mov bx, 0
    read:
    
    mov ah, 1
    int 21h
    
    cmp al, '0'
    jb quit;如果dl小于0
    cmp al, '9'
    jg quit;如果比9大
    
    mov dl, al
    mov ax, bx
    mul cl
    sub dl,'0'
    mov dh, 0
    add ax, dx
    mov bx, ax
    
    jmp read
    

    quit:
    ;输出结果
    mov ax, bx
    ;出栈
    pop bx
    pop cx
    pop dx
    
    ret 

output实现

规定接口

将ax寄存器中的数按照十进制的格式输出

实现思路

div reg
有两种格式。
一种是8位的,商放al,余数放ah
一种是16位的,商放ax,余数放dx

此时任务已经明确,即将ax中的数据按照一个格式输出,机器又提供了16位div的功能。而且用8位会出现溢出的情况,比如2560,就算得不对。(可以避免,代码放后面)

那就别折磨自己了。

用一个计数器,bx,cx都行,我这里用的是bx。记录运算过程中余数的个数。

至于具体的运算,每次将dx清为0, 然后再除cx=10,那么我们要的余数很自然的就放到了dx中,ax中存的还是商。

不用管那么多烦心事。

最后将栈里面的余数输出即可。

div 16

代码如下:

output:
    ;function : output ax as decimal based number
    ;input : ax
    ;return : void
	;压栈
	push dx
	push cx
	push bx
    push ax
    ;------------
    mov bx, 0

    mov cx, 10
    mov dx, 0
    div cx
    divLoop:
    push dx
    mov dx, 0
    div cx
    inc bx
    cmp dx, 0
    jnz divLoop

    outputLoop:
    pop dx
    mov dh, 0
    add dl, 30h;ascii
    ;char
    mov ah, 2
    int 21h
    ;
    dec bx
    cmp bx, 0
    jnz outputLoop
	;------------
	;出栈
    pop ax
	pop bx
	pop cx
	pop dx 
	ret

div 8

这里提供一个除法不会溢出的代码
ax中放被除数
cl中放除数
运行结束后
ax中放商
cl中放余数

在此稍微解释一下原理,第一次看容易晕。
应用了一个数学公式:
X/N = int(H/N) * 65536 + (REM(H/N) * 65536 + (L/N))

H表示X的高8位,L表示低8位。

int()表示取整,直接丢弃小数点后的数据。

rem()表示取模

这个公式其实也很好理解。

我们把x当成一个两位数。不过进制是65536进制。
那么按照算除法的经验,就是第一个除,算出来结果,然后得到的余数跟还剩下的位数进行组合
再进行计算。

不过就算理解了为什么这么做,下面的代码可能看起来也很迷。
在此简要说明,按照上述公式。为了得到最后的结果,我们应该先计算高位除除数的余数和商。
所以我们先保存了低位(push ax)。

然后计算了高位的余数和商(mov al, ah, mov ah, 0 div cl)

我们将此时得到的结果再度保存起来(mov dx, ax)

取出低位(pop ax)

按照计算公式我们需要拿到高位运算后的结果,这个结果目前保存在dh中.
我们取出后再次进行计算(mov ah, dh div cl)

得到的余数放在ah,商放在al。
为了符合我们定义的接口。将余数放cl(mov cl, ah)

之前计算得到的商放到ax的高位(mov ah, dl)

返回即可

读者要是认为自己理解了上述过程,可以去写写32/16的不溢出的除法。

            divdb:;16/8 not overflow
            ;
                 push dx
    			;--------------------
                 push ax                        

                 mov  al, ah
                 mov  ah, 0
                 div  cl
    
                 mov  dx, ax                    
                 pop  ax                        
                 mov  ah, dh                    
                 div  cl

                 mov  cl, ah
                 mov  ah, dl
    			;------------------------
                 pop  dx
                 ret

outputSomeData & inputSomeData

接口

inputSomeData:
从输入中读取cx个数,放到ds:si为首地址的内存段中
outputSomeData
从内存段ds:si,输出之后的CX个数

实现

多次调用之前的output 和 input即可做到。

比较简单。

中间的mov ah, 9 int 21h 是为了换行写的
不然堆一块太难看了。

代码如下:

    inputSomeData:
    ;function : read cx number from keyborad, end with nondigital charter
    ;input : cx = number of input, ds:si = the first address of  store number
    ;return : void
    push ax
    push cx
    push dx
    push si
    ;-----------------
    lea dx, CRLF
    inputSomeDataLoop:
    call input
    mov word ptr ds:[si], ax

    mov ah, 9
    int 21h 

    add si, 2
    loop inputSomeDataLoop
    ;----------------
    pop si
    pop dx
    pop cx
    pop ax
    ret
    
    outputSomeData:
    push cx
    push dx
    ;-----------------
    lea dx, CRLF
    outputSomeDataLoop:
    mov ax, word ptr ds:[si]
    call output

    mov ah, 9
    int 21h 

    add si, 2
    loop outputSomeDataLoop
    ;------------------
    pop dx
    pop cx
    ret

快速排序

伪码描述

//划分
int partition(int a[], int left, int right) {
	int begin = left;
	int bias = a[left++];//此处有多种选取方式 简单起见 选择数组的第一个元素
	while(left < right) {
		while(left <= right && a[left] < bias) ++left;//找到第一个大于等于的元素
		while(left <= right && a[right] > bias) ++right;//找到第一个小于等于的元素
		if(left < right) {//此处是为了防止交换错误
			swap(a[left], a[right]);
		}
	}
	//上述指针移动过程中必须要停和基准值相同的值 不然对于如下数据就会出现错误 2 1 1 2 1 1 3 4 5 2 6
	swap(a[begin], a[right]);//把基准放到中间
	return right;//返回基准的位置 便于判断
}
//实际快排函数
void quickSort(int a[], int left, int right) {
	if(left < right) {//一个元素不用再处理 超过了无法再处理
		int mid = partition(a, left, right);
		quickSort(a, left, mid - 1);
		quickSort(a, mid + 1, right);
	}
}
//快排接口
void quickSort(int a[], int length) {
	quickSort(a, 0, length - 1);
}

我们首先来实现最简单的快排接口 - quickSort。
调函数就行
这里需要注意的是如何计算左右指针的值。
这里的左指针为si, 右指针为di
长度为cx。
但是当前数据的长度为2byte。计算机按照字节寻址。
因为长度为cx的数据离起始点的偏移量为 2*cx - 2
即cx左移一位(乘2)再减去2

    quickSort:
    ;select the 0-th 
    ;select base number less put in the left bigger in the right
    ;input : cx = length of nums; ds:si = the first address of array
    ;return : ds:si = the first address of sorted array
    ;--calcuate first and last 
    push si
    push di
    push cx
    ;-------------
    shl cx, 1; word byte
    add cx, si
    mov di, cx
    sub di, 2
    call quickSortLeftRight
    ;-------------
    pop cx
    pop di
    pop si
    ret

接下来再来看递归调用的函数quickSortLeftRight
首先进行长度判断,如果left < right 就继续,否则直接退出。
汇编实现类似于
if(){}不是太好实现。
因此用的是相反的逻辑,即left >= right,就直接退出。
之后进行划分。
划分元素所在的位置我们放到了bp中。

然后就该进行递归调用了
当时我为了防止递归调用出现问题,每次都加了一个条件判断。
只有在合理的范围内才让调用。
后来仔细一想,其实没必要。
但是懒得删了。

注意这里我们要先保存si和di的值。(push si push di)

因为我们会修改si和di的值,用于递归函数的左右边界确定。

同理,对右侧的调用也是如此。

然后我们返回。

 quickSortLeftRight:
    ;input : ds:si = the first address of array; ds:di = the last address of array;
    ;return : ds:si = the first address of sorted array
    ;
    push si
    push di
    push bp
    ;----------------
    ;---if si >= di then quit
    cmp si, di
    jnb endQuickSortLeftRight
    ;---------
    call partition
    ;bp is the bias place
    ;
    ;-----------
    push si
    push di
    ;si - bp-2------------
    sub bp, 2
    mov di, bp
    call quickSortLeftRight
    pop di
    ;bp+2 - di------------
    add bp, 4
    mov si, bp
    call quickSortLeftRight
    pop si
    ;----------------
    endQuickSortLeftRight:
    pop bp
    pop di
    pop si
    ret

最后是划分函数-partition
首先我们明确接口,
si是起始,di是终点。
bp是划分标准在的位置。

我们首先拿到划分值(mov bx, word ptr ds:[si])
保存 起始点bp

然后左指针右移一位

此时我们需要去比较一下此时两个指针的大小,是否还满足si <= di

如果不满足,说明只有一个元素,不用进行划分。

(因为汇编的循环是一种do-while,不判断的话,结果会出问题)

然后左指针不断右移找大于等于的数据,没找到,则左指针继续右移,然后判断是否超过了右指针

超过了说明已经全部满足了条件,直接退出即可。
不然的话,继续找,当找到满足条件的值后,退出当前循环。

右指针左移也是同样的思路。不再赘述。

之后判断左右指针的相对位置,判断是否需要交换元素值。

退出的时候,交换基准和右指针的位置,将右指针的位置赋值给bp。

返回即可。

    partition:
    ;
    ;input : ds:si = the first address of array; ds:di = the last address of array
    ;return : bp = the address of bias
    push si
    push di
    push cx
    push bx
    push ax
    ;----------
    mov bx, word ptr ds:[si]
    ;
    mov bp, si; record the address of bias number
    add si, 2;
    ;
    ;if only have one element
    cmp bp, di
    jz endPartition
    ;----------
    LeftLessRight:
    findLeft:
    mov ax, word ptr ds:[si]
    cmp ax, bx
    jnb endFindLeft
    add si, 2
    ;-----------if out of size-
    cmp si, di
    ja endPartition
    ;-------------------------
    jmp findLeft
    endFindLeft:nop

    findRight:
    mov ax, word ptr ds:[di]
    cmp ax, bx
    jna endFindRight
    sub di, 2
    ;------------------if out of size
    cmp si, di
    ja endPartition
    ;-------------------------------
    jmp findRight
    endFindRight:nop


    ;--------avoid swap, si > di
    cmp si, di
    jnb endPartition
    ;swap two number
    mov ax, word ptr ds:[si]
    mov cx, word ptr ds:[di]
    mov word ptr ds:[di], ax
    mov word ptr ds:[si], cx
    ;
    ;add si, 2
    ;sub di, 2
    cmp si, di
    jna LeftLessRight
    ;-----------
    endPartition:
    ;-------swap the bais number
    mov ax, word ptr ds:[di]
    mov word ptr ds:[di], bx
    mov word ptr ds:[bp], ax
    ;----------
    mov bp, di
    ;----------
    pop ax
    pop bx
    pop cx
    pop di
    pop si
    ret

遇到的一些bug

之前我怕递归调用层数太多,栈不够用。于是就扩充了一下栈的位置,修改了栈的指针。
但是之后看栈的空间还有富裕。于是就又修改了栈的大小。
但是忘记改栈指针的大小了。
于是在我之后的操作中,会发现程序返回时的代码很奇怪。不是之前的代码,debug了好久。
查不出来问题。就当我快放弃的时候,看到了这个sp。
意识到是栈空间和栈指针的不匹配的问题。导致栈指针修改的内存空间是我的代码段的内存空间。
每次出栈和入栈,修改了程序段的数据。
所以返回的代码不对。
改完之后就正常运行了。

剩下的bug多是在调试快速排序的代码中遇到的。是逻辑bug。
不过通过单步调试,都解决了。

唯有这个栈指针错误的代码让我记忆深刻。
(因为调试的时候,mov ss, ax,后再执行一条指令 这是为了防止sp和ss所指定的内容不一样,比如这个时候发生中断。所以我将mov sp, 20h的代码写到了后面。导致我调试的时候都忽略了这个细节)
毕竟谁没事看sp。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值