一、题目要求
二、Huffman编码
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
至于Huffman树如何生成,见https://blog.csdn.net/qq_29519041/article/details/81428934
三、设计思路
通常用c语言来做这道题的话,我们第一时间想到的肯定是指针,用来指向它的左子树和右子树以及父节点,而在汇编中,并没有指针这个操作,于是乎,我想到了一个新的思路,类似于模拟一棵树。汇编语言中有一维数组,而一维数组足以解决这道题目了。
1、首先需要三个数组,parents数组,存放在生成Huffman树过程中,所有的父节点,lchird数组,存放在生成Huffman树过程中所有成为左孩子的节点,rchird同理。
2、对初始的频率(频率是小数,但我用了整数,只要能表达频率之间的大小关系即可)进行从小到大排序(我这里使用的是冒泡排序,因为简单实现)。特别提一下,加入可以直接找出最小的两个频率,不用排序当然也是可以的
3、将最小的两个频率,左小右大的规则放在左孩子数组和右孩子数组里面,将他们的和放到父亲数组里面,从这也就可以看出,父亲节点在数组中的位置和左孩子和右孩子是一样的,也就是所,他们可以直接通过自己的下标找到自己的父亲节点。然后将最小的两个删除,将和重新插入频率表中。
4.重复2,3,直到频率表中只有一个根节点的时候。
5、类似于下图,先找所求的叶子节点在哪个孩子数组,加入下面的图,黄色的叶子节点先在左孩子数组,因此给他的编码先加一个0,再找和他下标一样的父亲节点是谁,由图可以看到他的父亲节点是红色,接下来再看,红色属于还哪一个孩子数组,这里找的话,应该从孩子数组下标为1的地方开始找起(数组下标从0开始),因为和父亲下标一样或者比父亲下标小的左孩子和右孩子一定不是它本身。
6、这里看到红色的父节点又属于左孩子3,所有编码中再添加一个0,再重复5,6,直到找到的父节点在父亲数组的最后一个,也就是根节点,就结束查找。
7、将刚才的编码逆向输出。
四、汇编代码
include vcIO.inc
.data
WPL dword 0 ;带权路径和初值为0
print_wpl byte '带权路径和WPL为:%d',10,0
Huffman_code dword 20 dup(?)
parents dword 20 dup(?)
rchird dword 20 dup(?)
lchird dword 20 dup(?)
r_point dword ?
l_point dword ?
char byte 'abcdef'
number dword 2,5,3,6,8,1
length_number dword ?
l_num dword 0 ;左子树数组数量
r_num dword 0 ;右子树数组数量
p_num dword 0 ;父节点数组数量
now_num dword ?
len dword lengthof number
number1 dword 20 dup(?)
info_print byte '%c的哈夫曼编码是:',0,0
type_print byte '%d',0,0
speace_print byte ' ',10,0
temp dword ?
now_char dword ?
.code
main proc
mov length_number,lengthof number
mov ecx,lengthof number ;复制一份number的副本
mov esi ,0
loop2:
mov eax,number[esi*4]
mov number1[esi*4],eax
inc esi
loop loop2
mov ecx,lengthof number
loop_sort: ;循环最小的两个数
call sort
call add_array
dec ecx
cmp ecx,1
ja loop_sort
mov ecx ,len
mov esi,0
loop_find:
mov eax,number1[esi*4]
mov now_num,eax ;寻找每一个字符的哈夫曼编码,左零右一
mov bl,char[esi]
mov now_char,ebx
call find_code
inc esi
loop loop_find
invoke printf ,offset print_wpl,WPL
ret
main endp
sort proc ;排序
push ecx
push eax
push ebx
push edx
mov ecx ,0
mov eax ,0
mov ebx,1
out_loop: ;冒泡排序
cmp ecx,length_number
ja out1
inter_loop:
cmp ebx,length_number
je out2
mov edx,number[ebx*4]
cmp number[eax*4],edx
jl not_exchange ;前面比后面大则交换
xchg number[eax*4],edx
xchg number[ebx*4],edx
not_exchange:
inc eax
inc ebx
jmp inter_loop
out2:
mov eax ,0
mov ebx ,1
inc ecx
jmp out_loop
out1:
pop edx
pop ebx
pop eax
pop ecx
ret
sort endp
add_array proc ;将属于父节点的加入父节点数组,将属于子节点的加入子节点数组
push ecx
push eax
push ebx
push edx
mov ecx,l_num
mov eax,r_num
mov edx ,number[4]
mov ebx,number[0]
mov rchird[ecx*4], edx
mov lchird[eax*4], ebx
add edx,ebx
mov ecx ,p_num
mov parents[ecx*4],edx
inc l_num
inc r_num ;三个数组数量都加一
inc p_num
mov ecx,0 ;合并原始数组
mov ebx,1
loop1:
cmp ebx,length_number
ja out3
mov eax,number[ebx*4]
mov number[ecx*4],eax
inc ecx
inc ebx
jmp loop1
out3:
mov number[0],edx
dec length_number
pop edx
pop ebx
pop eax
pop ecx
ret
add_array endp
find_code proc
push ecx
push eax
push ebx
push esi
push edx
push now_num
mov ebx,0
mov ecx,p_num
mov temp,ecx
mov esi,0
loop_code:
mov eax,now_num
loop_r:
cmp rchird[esi*4] ,eax
je nextr
inc esi
cmp esi,ecx
jne loop_r
mov esi,0
loop_l:
cmp lchird[esi*4] ,eax
je nextl
inc esi
cmp esi,ecx
jne loop_l
nextr:
mov Huffman_code[ebx*4],1
jmp find_p
nextl:
mov Huffman_code[ebx*4],0
find_p:
inc ebx
mov edx,parents[esi*4]
mov now_num,edx
inc esi
cmp esi,temp
jb loop_code
pop now_num
pushad
invoke printf ,offset info_print,now_char
popad
mov ecx,ebx
mov eax ,ebx ;做乘法
mul now_num
add WPL ,eax
dec ebx
mov esi ,ebx
loop_print:
pushad
invoke printf ,offset type_print,Huffman_code[esi*4]
popad
dec esi
loop loop_print
pushad
invoke printf ,offset speace_print
popad
pop edx
pop esi
pop ebx
pop eax
pop ecx
ret
find_code endp
end main
五,运行结果
输入:
char byte 'abcdef'
number dword 2,5,3,6,8,1
输出:
a的哈夫曼编码是:0111
b的哈夫曼编码是:00
c的哈夫曼编码是:011
d的哈夫曼编码是:01
e的哈夫曼编码是:11
f的哈夫曼编码是:0110
带权路径和WPL为:59
六、总结
往往在遇到问题时,有两种办法,一种是克服,一种是绕过,c中的指针,解决这个问题很简单,但在汇编中,就应该想到用模拟构造一棵树来解决,有时候,换一种思路未尝不是一件好事。