哈工大数据结构、算法设计、计算机系统、软件构造等所有实验我都会发布在博客上,我会慢慢更新的,如果对你有帮助的话,可以多多关注一下。
目录
其他的实验链接
哈工大数据结构实验一 线性结构及其应用
哈工大数据结构实验1 算术表达式求值
哈工大数据结构实验2——二叉树存储结构的建立、遍历和应用
哈工大2019秋数据结构期末试题
对于本次实验,无非要解决的就是以下几个问题:
-
用什么数据结构去表示哈夫曼树
-
如何构造哈夫曼树
-
构造了哈夫曼树之后如何根据哈夫曼树将文本文件进行哈夫曼编码以及如何解码
本文将逐一阐述上述问题如何解决
- 首先,来看一下哈夫曼树的定义
定义:给定n个权值作为n个子叶节点,若树的带权路径最短,则这课树被称为哈夫曼树。 - 这里要注意的几个点有:
①只有叶节点才有权重,非叶节点不存储字符,也不带权重
②什么是带全路径?带权路径就是叶节点到根节点的路径长度乘以叶节点所带的权重。比如下面这个二叉树的带权路径为:
2x10 + 2x20 + 2x50 + 2x100 = 360
③哈夫曼树是带权路径最短的二叉树。
3. 好的,现在你已经知道哈夫曼树的定义,也了解了带权路径的定义。那么我们现在的目标就是构造出最短带权路径的二叉树。相信看到这里你已经有了构思,对于权重越大的叶节点,如果它离根节点越近,那么这个权重很大的叶节点对总的带权路径长度影响就越小。(right!)
比如对于上面的那个二叉树,对于权重为100的节点,如果我们把它放在离根节点最近的地方,50这个节点放在离根节点次近的地方,那么我们可以构造出一棵权重路径更小的二叉树。如图所示。
这棵树的权重路径为: 1x100 + 2x50 + 3x20 + 3x10 = 100 + 100 + 60 + 30 = 290 < 350。
看到这里,咱们的思路就清晰了,现在正式开始讲具体步骤。
假设有n个权重,构造出n个叶节点,n个权重分别为w1,w2…wn,哈夫曼树的构造规则如下:
- 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
- 在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
- 从森林中删除选取的两棵树,并将新树加入森林;
- 重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
以{5,6,7,8,15}为例,咱们试着构造一棵二叉树。
① 首先选出权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;也就是选出5和6,合并成11,剩下的树的节点权重为{7,8,11,15}
②在剩下的树的节点权重为{7,8,11,15},选出最小的两个权重{7,8}构造棵新树,权重为15,剩下的节点权重为{11,15,15}
③同理,在剩下的节点权重为{11,15,15}选出{11,15},构造新的节点,其权重为26,剩下的节点为{15,26}
④最后,选择{15,26}构造根节点即可。
上述构造出的 二叉树就是哈夫曼树。
正确性证明点击这里看即可,本篇不在阐述
好的,现在重新回到我们开始提出的三个问题。
1.用什么数据结构去表示哈夫曼树
首先,每个节点需要存储权重,以及左右儿子,同时,为了讲两个节点合并构造一个新的父节点,我们也需要在每个节点存储父节点。
最好的数据结构就是使用静态三叉链表。
所有节点构成一个数组,节点的父子关系可以用数组下标表示。
#define N 53 //带权重的n个叶子节点数,根据文件中字符种类的个数来确定
#define M 2*N-1 //n个叶子节点的哈夫曼树具有2*n-1个节点
typedef struct{
float weight;//权重
int lchild;//左儿子
int rchild;//右儿子
int parent;//父亲
}node;//静态三叉链表
typedef node huffman[M]; //哈夫曼树
typedef char *huffmancode[N];//存储每个字符的哈夫曼编码表
2. 如何构造哈夫曼树
我的思路是:
- 给定n个权重,那么叶节点就有n个,非叶节点n-1个。那么首先初始化n个叶节点,包括初始化叶节点的权重,左儿子、右儿子、父亲初始化为-1。父亲为-1表示一开始所有的叶节点还没有选中来构造树。
- 然后就开始选择两个叶节点来构造一个新的节点,这个节点的权重为两个儿子的权重之和。选择策略是:从所有的没有父亲的节点找出两个权重最小的节点。这个功能我在函数selectmin中实现,其中参数s1,和s2传递的是引用,在函数内改变s1和s2的值,会直接改变函数外s1和s2的值。
具体实现如下:
void selectmin(huffman &T,int k,int &s1,int &s2):选出权重最小的两个节点
void selectmin(huffman &T,int k,int &s1,int &s2){
//选出两个权重最小的节点
int min=1000000,tmp=0;
for(int i=0;i<=k;i++){
if(T[i].parent==-1){
if(min>T[i].weight){
min=T[i].weight;
tmp=i;
}
}
}
s1=tmp;
min=100000;
tmp=0;
for(int j=0;j<=k;j++){
if((T[j].parent==-1)&&(j!=s1)){
if(min>T[j].weight){
min=T[j].weight;
tmp=j;
}
}
}
s2=tmp;
}
void CreatTree(huffman &T,float *w,int n) {
//构造哈夫曼树
if(n<=1)
return ;
int i;
for( i=0;i<n;i++){
//初始化哈夫曼树的n个叶节点并赋予权重
T[i].weight=w[i];
T[i].lchild=-1;
T[i].rchild=-1;
T[i].parent=-1;
}
for(;i<