数据结构-----哈夫曼编译码器
- 题目
- 题目描述
- 基本要求
- 算法分析
- 代码实现
- 初始化
- 编码
- 解码
- 打印代码
- 打印哈夫曼树
- 总结
题目
题目描述
- 利用哈夫曼编码进行信息通信可大大提高信道利用率,缩短信息传输时间,降低传输成本。
要求:在发送端通过一个编码系统对待传数据预先编码;在接收端将传入的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼的编/译码系统。
基本要求
- 基本要求
系统应具有以下功能:
① I:初始化.从终端读入字符集大小n及n个字符和n个权值,建立哈夫曼树,井将它存于文件HuffmanTree中。
② C:编码。利用已建立好的哈夫曼树(如不在内存,则从文件HuffmanTree中读入)。对文件tobetrans中的正文进行编码,然后将结果存入文件codefile中。
③ D:解码。利用已建立好的哈夫曼树将文件codefile中的代码进行译码,结果存入testfile中。
④ P:打印代码文件。将文件codefile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件codeprint中。
⑤ T:打印哈夫曼树。将已在内存中的哈夫曼树直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件treeprint中。
算法分析
- 本题例主要用到3个算法如下:
① 哈夫曼编码。在初始化(I)的过程中,要用输入的字符和权值建立哈夫曼树并求得哈夫曼编码。先将输入的字符和权值放到一个结构体数据中,建立哈夫曼树,将计算所得的哈夫曼编码存储到另一个结构体数组中。
② 串的匹配。在解码(D)的过程中,要对已经编码过的代码进行译码,可利用循环,将代码中与哈夫曼编码长度相同的串与这个哈夫曼编码进行比较,如果相等就回显并存入文件。
③二叉树的遍历。在打印哈夫曼树(T)的过程中,因为哈夫曼树也是二叉树,所以就要利用二叉树的前序遍历将哈夫曼树输出。
代码实现
代码部分参考了《数据结构-C语言版第2版》这本书中代码以及C++风格进行书写,测试文件tobetrans中的内容为"ABCABC"
初始化
题目要求从终端读入字符集大小n及n个字符和n个权值,建立哈夫曼树,并将它存于文件HuffmanTree中,可以先将哈夫曼树构建起来,得到哈夫曼树及其对应叶子节点的编码,再通过文件的输入输出将这些编码写入文件HuffmanTree中即可
- 构造哈夫曼树
/*找出森林集合中根权值最小的两个*/
void Select(HuffmanTree HT,int len,int &s1,int &s2)
{
int i,min1=0x3f3f3f3f,min2=0x3f3f3f3f;//先赋予最大值
for(i=1;i<=len;i++)
{
if(HT[i].weight<min1&&HT[i].parent==0)
{
min1=HT[i].weight;
s1=i;
}
}
int temp=HT[s1].weight;//将原值存放起来,然后先赋予最大值,防止s1被重复选择
HT[s1].weight=0x3f3f3f3f;
for(i=1;i<=len;i++)
{
if(HT[i].weight<min2&&HT[i].parent==0)
{
min2=HT[i].weight;
s2=i;
}
}
HT[s1].weight=temp;//恢复原来的值
}
/*构建哈夫曼树*/
void CreatHuffmanTree(HuffmanTree &HT,int n)
{
//构造赫夫曼树HT
int m,s1,s2,i;
if(n<=1) return;
m=2*n-1;
HT=new HTNode[m+1]; //0号单元未用,所以需要动态分配m+1个单元,HT[m]表示根结点
for(i=1;i<=m;++i) //将1~m号单元中的双亲、左孩子,右孩子的下标都初始化为0
{ HT[i].parent=0; HT[i].lchild=0; HT[i].rchild=0; }
cout<<"请输入叶子结点的字符和权值:\n";
for(i=1;i<=n;++i) //输入前n个单元中叶子结点的权值
cin>>HT[i].s>>HT[i].weight;
/*――――――――――初始化工作结束,下面开始创建赫夫曼树――――――――――*/
for(i=n+1;i<=m;++i)
{ //通过n-1次的选择、删除、合并来创建赫夫曼树
Select(HT,i-1,s1,s2);
//在HT[k](1≤k≤i-1)中选择两个其双亲域为0且权值最小的结点,
// 并返回它们在HT中的序号s1和s2
HT[s1].parent=i;
HT[s2].parent=i;
//得到新结点i,从森林中删除s1,s2,将s1和s2的双亲域由0改为i
HT[i].lchild=s1;
HT[i].rchild=s2; //s1,s2分别作为i的左右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight; //i 的权值为左右孩子权值之和
}
}
- 哈夫曼编码
/*哈夫曼树编码*/
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{
//从叶子到根逆向求每个字符的赫夫曼编码,存储在编码表HC中
int i,start,c,f;
HC=new char*[n+1]; //分配n个字符编码的头指针矢量
char *cd=new char[n]; //分配临时存放编码的动态数组空间
cd[n-1]='\0'; //编码结束符
for(i=1;i<=n;++i)
{ //逐个字符求赫夫曼编码
start=n-1; //start开始时指向最后,即编码结束符位置
c=i;
f=HT[i].parent; //f指向结点c的双亲结点
while(f!=0)
{ //从叶子结点开始向上回溯,直到根结点
--start; //回溯一次start向前指一个位置
if(HT[f].lchild==c)
cd[start]='0'; //结点c是f的左孩子,则生成代码0
else
cd[start]='1'; //结点c是f的右孩子,则生成代码1
c=f;
f=HT[f].parent; //继续向上回溯
} //求出第i个字符的编码
HC[i]=new char[n-start]; // 为第i 个字符编码分配空间
strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
}
delete cd; //释放临时空间
}
- 将编码写入到文件HuffmanTree
这里通过循环将构造好的哈夫曼编码直接写入到文件
//编码写入文件HuffmanTree.txt
void printCreatHuffmanCode(HuffmanTree HT, HuffmanCode HC,int n)
{
ofstream hFile("HuffmanTree.txt");
if (!hFile.is_open()) {
cout << "文件HuffmanTree.txt打开失败." << endl;
exit(1);
}
cout<<"各字符对应的编码如下:"<<endl;
for (int i = 1; i <=n; i++)
{
cout << HT[i].s << HC[i] << endl;
hFile << HT[i].s << HC[i]<<endl;
}
hFile.close();
cout << "成功写入文件HuffmanTree.txt" << endl;
}
初始化部分运行截图如下:
编码
思路:通过文件逐行读出字符,并将其存放到数组,通过遍历该数组以及哈夫曼树,从而将对应的编码写入到文件codefile.txt中
//编码部分
void Huffmancode(HuffmanTree HT,HuffmanCode HC,int n) {
// 函数用于对 "tobetrans.txt" 文件中的内容进行编码,并将结果存储在 "codefile.txt" 文件中
// 假设 "tobetrans.txt" 包含要编码的内容,每一行表示一个要编码的字符串。
ifstream tFile("tobetrans.txt");//读
ofstream cFile("codefile.txt");//写
if (!tFile.is_open()) {
cout << "文件tobetrans.txt打开失败.\n"<<endl;
exit(1);
}
if (!cFile.is_open()) {
cout << "文件codefile.txt打开失败.\n"<<endl;
exit(1);
}
char tobetrans[100];//临时存放文件中每一行的字符
// 逐行读取内容
while (tFile.getline(tobetrans, sizeof(tobetrans))) {//行不空
int m= strlen(tobetrans);//数组长度,即每一行字符个数
cout<<"tobetrans内容:"<<tobetrans;
cout<<endl;
for (int i = 0; i <m; i++) {//遍历数组
for(int j=1;j<=n;j++) //遍历树节点(0号单元为根节点未启用)
{
if (HT[j].s == tobetrans[i]) {//相等则将其对应的编码写入文件
cFile << HC[j];
}
}
}
}
tFile.close();
cFile.close();
cout<<"成功写入文件codefile.txt"<<endl;
}
编码部分运行截图如下:
解码
思路:和编码部分一样,先从文件逐行读出存入数组,接着再遍历该数组和树的节点,从而将对应的字符写入到testfile.txt中
//译码部分
void HuffmanDecode(HuffmanTree HT, int n) {
//函数用于利用已建立好的哈夫曼树将文件"codefile.txt"中的代码进行译码,结果存入"testfile.txt"中
ifstream codeFile("codefile.txt");//读
ofstream testFile("testfile.txt");//写
if (!codeFile.is_open()) {
cout << "文件codefile.txt打开失败.\n"<<endl;
exit(1);
}
if (!testFile.is_open()) {
cout << "文件testfile.txt打开失败.\n"<<endl;
exit(1);
}
char pwd[100];
codeFile.getline(pwd, sizeof(pwd));//每次读取一行放入数组pwd中进行译码
int len = strlen(pwd);
int i = 0;
int node = 2 * n - 1;//从根节点开始
while (i < len) { // 循环遍历读取数组中的每个字符
while (HT[node].lchild != 0 && HT[node].rchild != 0) { // 当当前节点不是叶子节点时循环
if (pwd[i] == '0') { // 如果当前读取的字符是 '0'
node = HT[node].lchild; // 移动到当前节点的左孩子节点
} else if (pwd[i] == '1') { // 如果当前读取的字符是 '1'
node = HT[node].rchild; // 移动到当前节点的右孩子节点
}
i++; // 移动到下一个字符
}
testFile << HT[node].s; // 将叶子节点对应的字符写入到输出文件中
node = 2 * n - 1; // 将节点移动回到根节点
}
codeFile.close();
testFile.close();
cout << "成功写入文件testfile.txt" << endl;
}
解码部分运行截图如下:
打印代码
思路:通过文件逐行读出二进制码,在根据题目要求将对应的格式输出到终端输入到文件(我这里以每5行为例)
//打印代码文件
void Print()
{
//函数用于将文件codefile.txt以紧凑格式显示在终端上,每行5个代码。
//同时将此字符形式的编码文件写入文件codeprint.txt中。
char str[1000];//存储codefile.txt的0-1编码
int num = 0;
ifstream file1("codefile.txt");//读出
ofstream file2("codeprint.txt");//写入
if(file1.fail()){
cout<<"文件codefile.txt打开失败"<<endl;
exit(0);
}
if(file2.fail()){
cout<<"文件codeprint.txt打开失败"<<endl;
exit(0);
}
file1.getline(str, 10000);//读取每一行
cout<<"str内容"<<str;
cout<<endl;
cout<<"文件codefile.txt内容显示如下:"<<endl;
while(str[num]){
if(num%5==0&&num!=0)//每输出5个字符换行,由于数组下标从0开始所以要忽略num==0这个情况
{
cout<<endl;
file2<<endl;//文件里也是每5个字符换行
}
cout<<str[num];
file2<<str[num];//写入文件
num++;
}
cout<<endl;
file1.close();
file2.close();
cout<<"成功写入文件codeprint.txt"<<endl;
}
打印代码部分运行截图如下:
打印哈夫曼树
这里结合树的前序遍历,并以凹入表的形式进行输出
void PrintHuffmanTree(HuffmanTree HT, int root, int level) {
// 递归打印哈夫曼树的结构(凹入表形式)
if (root != 0) {
// 打开文件 treeprint.txt
ofstream treeFile("treeprint.txt", ios::app); // 使用 ios::app 追加模式
if (treeFile.fail()) {
cout << "文件 treeprint.txt 打开失败" << endl;
exit(0);
}
// 输出当前节点信息(以凹入表形式)
cout << string(4 * level, ' '); // 控制缩进
cout << HT[root].weight;//输出节点权值
if (HT[root].s) {
cout << " (" << HT[root].s << ")";//节点字符
}
cout << endl;
treeFile << string(4 * level, ' ');
treeFile << HT[root].weight;
if (HT[root].s) {
treeFile << " (" << HT[root].s << ")";
}
treeFile << endl;
// 递归打印左右子树
PrintHuffmanTree(HT, HT[root].lchild, level + 1);
PrintHuffmanTree(HT, HT[root].rchild, level + 1);
}
}
打印哈夫曼树运行截图如下:
总结
关于如何书写一个哈夫曼编译码器,以上代码大家可以参考,仅供参考!!!毕竟我写的也不是很优秀,只能做到题目的要求,代码中还有许多可以优化的地方,介于自己能力有限,就先分享这么多,大家如果在参考以上代码存在问题时,需要我自己书写的完整代码可以私信我哦,在此感谢各位大佬的支持!