前言:
实验三是针对图的应用,这里主要的是赫夫曼树的简单构建和编码解码过程,相信只要了解了具体的编码细节,那么解码过程便迎刃而解,这里的赫夫曼树构建主要是依据用户输入的字符串序列,以字符串中各个字符的频度为基准建立赫夫曼树,实验具体过程,包括数据结构定义,抽象数据结构,各部分核心算法设计如下,源码附在最后,建议大致浏览后再看源码喔。
实验三:树的应用
问题描述
对任意输入的一段英文,为每个字符编制其相应的赫夫曼编码;并利用该编码为任意输入的0、1序列进行解码.
基本要求
一个完整的系统应具有以下功能:
(1)初始化 从终端读入一段英文字符,统计每个字符出现的频率,建立赫夫曼树,并将该树存入某文件;
(2)编码 利用建好的赫夫曼树对各字符进行编码,用列表的形式显示在屏幕上,并将编码结果存入另一文件中;
(3)解码 利用保存的赫夫曼编码,对任意输入的0,1序列能正确解码;
实验基本原理与设计
1.数据结构的设计
1):依据实验要求构造相关结构体
//赫夫曼树结点
typedef struct HUffMANTREE {
int weight;//权值
int parent, lchild, rchild;//双亲左右孩子
char CH;
}HTNode, * Tree;//赫夫曼树
//赫夫曼编码指针向量
typedef char** HuffmanCode;
//权值种类结点
typedef struct NUMBERNODE {
int ww;//整合过程中作为标识,0则未整合,1表示已整合,>1值表示数据重复次数,即为频度,权值
char cc;
NUMBERNODE* next;
}NNode, * Num;
//整合过程权值指针向量
typedef NNode** NumberWeight;
2):抽象数据类型描述(ADT)
ADT Huffman{
数据对象:D={
HT[i]| HT[i]∈HTNode,i=1,2,3…,n,n≥0}
数据关系:R1={
< HT[i-1], HT[i]>| HT[i-1], HT[i]∈D,i=1,2,3…,n,n≥0}
基本操作:
InitHffman(HuffmanTree &HT, NUMBER w, int n)
初始条件:权值w,字符种类数n已整合
操作结果:以w中所给权值,2*n-1个赫夫曼节点个数初始化赫夫曼树HT
Status CreatHuffman(HuffmanTree&HT,HuffmanCode& HC, int n)
初始条件:赫夫曼树已初始化,字符种类数已知
操作结果:由初始化的赫夫曼树构造完整赫夫曼树,对每个字符赫夫曼编码,并将最后结果保存至指定文件
printHT(HuffmanTree HT,int n)
初始条件:赫夫曼树已存在
操作结果:打印赫夫曼树
printHC(HuffmanCode HC, HuffmanTree HT,int n)
初始条件:赫夫曼树赫夫曼编码已存在
操作结果:打印赫夫曼编码
Decode(HuffmanTree &HT,int n)
初始条件:赫夫曼树解码
操作结果:对输入的以2结尾的01序列根据已知的赫夫曼编码解码
}ADT Huffman
2.核心算法设计
(1):字符串整合函数
Status MangeStr(char str, Num w)**
功能简介:
此函数用于对用户输入的一系列字符str进行整合,将整合结果以结构体数组形式储存(Num *w),其中w作为这个结构体数组的指针向量,函数返回整合后字符串中字符的种类个数。
参数
- str:输入字符串
- w: 权值结点向量
实现
-
进入函数,首先以str字符串中每个字符作为元素,生成一个NNode结构体类型的链表(Num head),其中ww字段均置为0以表示此字符未整合。
-
然后以指向此链表的Num cur游标作为while循环条件,如果ww值为0则表示此字符未被整合,为w指针向量分配内存,储存相关信息,并将此时游标cur指向ww值置为1表示已整合
-
而后另设一游标p从cur->next开始遍历当前链表(可以预见cur之前的字符均被整合过),若其中含有与此时w储存字符相同且其ww值为0(未整合)则w权值+1,且将此时p游标指向元素ww值置为1,得到当前w表示的字符的所有整合
-
如此往复直至cur指向NULL(即整合结束),便得到整合后的所需内容,返回退出循环时指针向量的下标j即为字符种类个数
-
另由于整合过程中head链表涉及到对于堆内存的分配且之后程序不再使用此链表,所以在函数结束时利用Destroy函数释放内存
(2):赫夫曼树寻找最小无双亲两权值下标
void Select(HuffmanTree& HT, int range, int& s1, int& s2)
功能简介:
在已有且初始化后的赫夫曼树中,以参数值range为范围(1-range)在赫夫曼树HT中寻找两个最小权值无双亲结点下标,并由s1,s2分别储存其下标。
参数
- HT:待搜索赫夫曼树
- range:搜索范围
- s1:储存搜索结果1
- s2:储存搜索结果2
实现
-
以赫夫曼树HT中的权值为依据,用一个for循环为数组save赋值(为契合赫夫曼树的储存,同样采取0号单元舍弃不用),其中为save赋值的元素需满足双亲为0
-
然后利用选择排序法将数组save中按权值升序排列,此时得到的save[1],和save[2]即为此时(1-range)范围内最小两权值
-
再次利用一个for循环结合break语句,分别寻找符合这两个权值条件的对应赫夫曼树中的元素,并将其下标赋值给相应的s1,s2,其中寻找s2的for循环还需满足此时的下标不等于已经赋值的s1
(3):赫夫曼编码
功能简介:
赫夫曼编码部分实现于函数Status CreatHuffman(Tree& HT, HuffmanCode& HC, int n)内部,是此函数的一个子过程,依据当前构建好的赫夫曼树来对每个字符进行编码,也可以将它单独提出来作为一个函数,不冲突
实现原理
- 每个字符在构建好的赫夫曼树中是一个叶子节点,从叶子节点开始,每次寻找其父节点,识别其是左节点还是右节点(左为0,右为1),记录01数据作为路径,知道父节点为0,即到达根节点,完成编码
- 用cd字符数组存储编码,n种字符构成的赫夫曼树,编码长度不超过n-1,为了匹配这样的从叶子结点的回溯过程,从cd数组末尾开始填充路径,用site标识当前哈夫曼编码的开始位置,一条编码完毕后,为赫夫曼编码指针向量分配相应编码大小内存,并利用strcpy字符串复制函数,从site位置开始赋值
- 赫夫曼解码过程便可参照编码过程,解码即为编码的逆过程,详见源码void Decode(Tree& HT, int n)函数
此部分源码如下:
//赫夫曼编码
HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));//赫夫曼编码头指针向量,0号单元未用
cd = (char*)malloc(n * sizeof(char));//编码长度最长为n-1,最后一位作为结束标识
cd[n - 1] = '\0';
for (i = 1; i <= n; i++)//共有n个字符,对应n串编码
{
site = n - 1;
c = i, f = HT[i].parent;
for (; f != 0; c = f, f = HT[f].parent)
if (HT[f].lchild == c)
cd[--site] = '0';
else
cd[--site] = '1';
HC[i] = (char*)malloc((n - site) * sizeof(char));//为字符的赫夫曼编码分配空间
strcpy(HC[i], &cd[site]);//从site位置开始复制到HC[i]
}
源码
//实现对二叉树的一个指定操作或用二叉树解决实际问题
//问题描述,对任意输入的一段英文,为每个字符编制其相应的哈夫曼编码
//并利用该编码为任意输入的0,1序列进行解码
#define _CRT_SECURE_NO_WARNINGS
#include<