实验目的
- 完成Huffman Tree 编码、译码系统的设计
- 自行设计测试数据
- 自行找一段本文,统计本文中每一个字符出现的频率。
- 理解建立Huffman Tree(编码)过程
实验设备与环境
Dev-C++ 5.11
实验过程及结果分析
1. 构造哈夫曼树
设已知n个结点的权值,根据这些权值构成森林F,森林中的每棵二叉树有且只有一个根结点,权值与之相对应,左右子树为空。
A. 在森林F中选取两颗二叉树根结点的权值最小、次小的树,把它们作为左右子树合成一棵新树,新树权值为左右子树权值的和。
B. 不断重复上述步骤,直到所有树合成一棵树为止。
C. 在合成新树的过程中,每次需要在森林中选取权值最小的两个根结点。因此,引入函数SelectSmall(least,less,i),在hufftree的前i个向量范围中,找到最小的根结点下标least和次小的根结点下标less。
D. 实现细节中,应注意为树中所有结点预留向量空间,初始化Hufftree,注意只有一个根结点。一共需要执行n - 1次合并根结点。
2. SelectSmall函数
A. 函数SelectSmall在于搜索当前森林中权值最小和次小的两个结点,并将下标分别记录到least、less中。
B. 每一次搜索前先找到当前下标值最小的的单结点(parent域为-1),将下标记录到least中。
C. 寻找最小least下标的过程,可以将将当前least下标的结点的权值与剩下的所有单结点权值作比较,每一次将更小的权值的结点下标赋值给least。
D. 寻找次小less下标的过程同寻找least下标的过程类似,需要在查找过程中加入判断条件,即下标值不等于least,这样查找的结果即为次小下标。
3. 哈夫曼编码
A. 每一个符号的Huffman编码是一个0/1串,因此采用串位储存的方式。本函数采用整数向量存储第i个符号的Huffman编码,并返回该编码串。
B. 第i个符号储存在Huffman树中第i个叶子结点中。为计算它的编码向量,需要向根结点追溯它的结点路径。在路径中左孩子关系记作编码0,右孩子关系记作编码1。由于编码是从叶子结点向根结点逐个读出,次序恰好逆置,因此每个编码数字应该添加在编码向量的首部,这样才能得到编码向量的正确次序。
4. 哈夫曼译码
A. 当已知编码文件,需要还原信息符号串时,需要依据编码时的原Huffman树进行译码。函数Decode(vector &source)中参数source是编码文件(0/1向量),返回值是原信息符号串。
B. 每个符号的译码工作都是从Huffman树的根向叶子结点的下行过程。逢0向左孩子下行,逢1向右孩子下行。当下行遇到叶子结点时,该叶子中的data域的值就是译码符号。
C. 每找到一个叶子结点并取其data域的值,再将当前结点再次置为根结点,直到找到最后一个叶子结点的data值。
5. 实验结果
原字符串以“good good study”为例子,实验结果如下:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
data | g | o | d | s | t | u | y | ||||||||
weight | 0.13 | 0.27 | 0.2 | 0.13 | 0.07 | 0.07 | 0.07 | 0.07 | 0.14 | 0.14 | 0.26 | 0.28 | 0.46 | 0.55 | 1.0 |
parent | 10 | 13 | 12 | 10 | 8 | 8 | 9 | 9 | 11 | 11 | 12 | 13 | 14 | 14 | -1 |
lchild | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 4 | 6 | 0 | 8 | 2 | 1 | 12 |
rchild | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 5 | 7 | 3 | 9 | 10 | 11 | 13 |
实验代码
main.cpp
#include <iostream>
#include "Huffman.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
char ss[]="good good study";
//char ss[]="BACECDC";
/*char data[] = {'w','h','e','r',' ','t','i','s','a','w','l',',','y'};
double weight[] = {0.03,0.08,0.17,0.08,0.19,0.06,0.08,0.06,0.08,0.06,0.06,0.03,0.03};
*/
char data[] = {'g','o','d',' ','s','t','u','y'};
double weight[] = {0.13,0.27,0.20,0.13,0.07,0.07,0.07,0.07};
int len=strlen(data);
HuffmanNode a[len] ;
for(int i=0;i<len;i++)
{
a[i].data = data[i];
a[i].weight = weight[i];
a[i].lchild = a[i].rchild = -1;
a[i].parent = -1;
}
vector<HuffmanNode> t(a,a+len);
HuffmanTree hu(t);
//print(hu.hufftree,2*len-1);
cout<<"密码表:"<<endl;
for(int j=0;j<len-1;j++)
{
vector<int>code = hu.GetCode(j);
cout<<hu.hufftree[j].data<<":";
for(int i=0;i<code.size();i++)
cout<<code[i];
cout<<endl;
}
cout<<"---------------"<<endl;
cout<<"原字符串为:"<<endl<<ss<<endl;
cout<<"---------------"<<endl;
cout<<"编码后:"<<endl;
vector<int>encode;
for(int i=0;i<strlen(ss);i++)
{
int j;
for(j=0;j<hu.hufftree.size();j++)
if(ss[i] == hu.hufftree[j].data)
break;
vector<int>code = hu.GetCode(j);
encode.insert(encode.end(),code.begin(),code.end());
}
for(int i=0;i<encode.size();i++)
cout<<encode[i];
cout<<endl<<"---------------"<<endl;
cout<<"译码后:"<<endl<<hu.Decode(encode);
return 0;
}
Huffman.h
#include<bits/stdc++.h>
using namespace std;
struct HuffmanNode
{
char data; // 待编码的符号
double weight; // 符号出现的频率
int parent, lchild, rchild; // 父结点、左, 右孩子结点的位置
};
class HuffmanTree
{
//vector<HuffmanNode> hufftree; // 树中所有结点的存储空间
int n; // 叶子结点数
void SelectSmall(int &least,int &less,int i);
public:
vector<HuffmanNode> hufftree;
HuffmanTree(vector<HuffmanNode> &leafs);
vector<int> GetCode( int i );
string Decode(vector<int> &source);
// string Encode()
};
HuffmanTree::HuffmanTree(vector<HuffmanNode> &leafs)
{
n = leafs.size();
hufftree.resize(2*n-1);
for(int i=0;i<n;i++)
{
hufftree[i].data = leafs[i].data;
hufftree[i].weight = leafs[i].weight;
hufftree[i].parent = hufftree[i].lchild
= hufftree[i].rchild
= -1;
}
for(int i=n;i<2*n-1;i++)
{
int least,less;
SelectSmall(least,less,i);
hufftree[least].parent = hufftree[less].parent = i;
hufftree[i].parent = -1;
hufftree[i].lchild = least;
hufftree[i].rchild = less;
hufftree[i].weight = hufftree[least].weight
+ hufftree[less].weight;
}
}
//--------------------------
void HuffmanTree::SelectSmall(int &least,int &less,int i)
{
for (int j = 0; j < i; j++)
if (hufftree[j].parent == -1)
{
least = j;
break;
}
for (int j = 0; j < i; j++)
if (hufftree[j].parent == -1 && hufftree[least].weight > hufftree[j].weight)
least = j;
for (int j = 0; j < i; j++)
if (hufftree[j].parent == -1&&j!=least)
{
less = j;
break;
}
for (int j = 0; j < i; j++)
if (hufftree[j].parent == -1 && hufftree[less].weight > hufftree[j].weight&&j != least)
less = j;
}
//--------------------------
vector<int> HuffmanTree::GetCode( int i )
{
vector<int>code;
int p = i;
int parent = hufftree[i].parent;
while(parent!= -1)
{
if(hufftree[parent].lchild == p)
code.insert(code.begin(),0);
else
code.insert(code.begin(),1);
p = parent;
parent = hufftree[parent].parent;
//cout<<code[i];
}
return code;
}
//--------------------------
string HuffmanTree::Decode(vector<int> &source)
{
string target ="";
int root = hufftree.size() -1;
int p = root;
for(int i=0;i<source.size();i++)
{
if(source[i] == 0)
p = hufftree[p].lchild;
else
p = hufftree[p].rchild;
if(hufftree[p].lchild == -1
&& hufftree[p].rchild == -1)
{
target = target + hufftree[p].data;
p = root;
}
}
return target;
}
void print(vector<HuffmanNode> hT,int n)
{
cout << "index weight parent lChild rChild" << endl;
cout << left; // 左对齐输出
for (int i = 0; i < n; ++i)
{
cout << setw(5) << i << " ";
cout << setw(6) << hT[i].weight << " ";
cout << setw(6) << hT[i].parent << " ";
cout << setw(6) << hT[i].lchild << " ";
cout << setw(6) << hT[i].rchild << endl;
}
}