本篇是上一篇赫夫曼树构建与编码的后续,稍微详细讲一下解码的算法。
Huffman解码算法流程:
1.定义指针p指向赫夫曼树结点,实际是记录结点数组的下标;
2.定义指针i指向编码串,定义ch逐个取编码串的字符;
3.初始化:读入编码串,设置p指向根结点,i为0;
4.执行以下循环:
a)取编码串的第i个字符放入ch;
b)如果ch是字符0,表示往左孩子移动,则p跳转到右孩子;
c)如果ch是字符1,表示往右孩子移动,则p跳转到右孩子;
d)如果ch非0非1,表示编码串有错误,输出error表示解码失败;
e)检查p指向的结点是否为叶子;
i.如果是叶子,输出解码,p跳回根节点;
ii.如果不是叶子,设置ch为3;
f) 继续循环,一直到编码串末尾;
5.循环执行完后,如果ch值为3,输出解码失败,否则成功结束。
解码关键:赫夫曼编码是一种前缀编码,解码时不会因为编码串混肴;
例如:
字符串AACACBDEDDDD 编码后我们可以获得对应的前缀:
A: 10;B: 1110;C: 110;D: 0;E: 1111
它对应的编码串即为:
下面是参考代码,注释我也打得比较详细了,算是边对着代码边讲解吧:
#include <iostream> #include <string> #include <cstring> using namespace std; #define error -1 #define ok 1 const int MaxW = 9999; //假设结点权值不超过9999 //定义huffman树结点类 class HuffNode { public: int weight; //权值 int parent; //父结点下标 int leftchild; //左孩子下标 int rightchild; //右孩子下标 char data; //解码数据 }; //定义huffman树类 class HuffMan { private: void MakeTree(); //建树,私有函数,被公有函数调用 void SelectMin(int pos, int *s1, int *s2); //从1到pos的位置找出权值最小的两个结点,结点下标存在s1和s2中 public: int len; //结点数量 int lnum; //叶子数量 HuffNode *huffTree; //huffman树,用数组表示 string * huffCode; //每个字符对应的huffman编码 void MakeTree(int n,int wt[],char data[]); //公有函数,被主函数main调用 void Coding(); //公有函数,被主函数main调用 void Destroy(); int Decode(const string codestr,char txtstr[]); }; //构建huffman树 void HuffMan::MakeTree(int n,int wt[],char data[]) //参数是叶子结点数量和叶子权值 {//公有函数,对外接口 int i; lnum = n; len = 2*n-1; huffTree = new HuffNode[2*n]; huffCode = new string [lnum+1]; //位置从1开始计算 //huffCode实质是个二维字符数组,第i行表示第i个字符对应的编码 //huffman树huffTree初始化 for(i=1;i<=len;i++) { huffTree[i].weight = wt[i-1]; //第0号不用,从1开始编号 huffTree[i].data=data[i-1]; //解码用 } for(i=1;i<=len;i++) { if(i>n) huffTree[i].weight=0; //前n个结点是叶子,已经设值 huffTree[i].parent = 0; huffTree[i].leftchild = 0; huffTree[i].rightchild=0; } MakeTree(); //调用私有函数建树 } void HuffMan::SelectMin(int pos,int *s1,int *s2) //找出最小的两个权值的下标 //函数采用地址传递的方法,找出的两个下标保存在s1和s2中 { int w1,w2,i; w1=w2=MaxW; //初始化w1和w2为最大值,在比较中会被实际的权值替换 *s1=*s2=0; for(i=1;i<=pos;i++) //对结点数组进行搜索 { if(huffTree[i].weight < w1 && huffTree[i].parent == 0) { //如果i结点的权值小于w1,且第i结点是未选择的结点(父亲为0) w2 = w1; //把w1,s1保存到w2,s2,即原来的第一最小值变成第二最小值(不理解?) *s2 = *s1; w1 = huffTree[i].weight; //把i结点的权值和下标保存到w1,s1,作为第一最小值 *s1=i; } else if(huffTree[i].weight < w2 && huffTree[i].parent == 0) { w2=huffTree[i].weight;*s2=i; } //否则如果i结点的权值小于w2,且i结点是未选中的,把i结点的权值和下标保存到w2和s2,作为第二最小值 } } void HuffMan::MakeTree() {//私有函数,被公有函数调用 int i,s1,s2; //构造huffman树HuffTree的n-1个非叶子结点 for(i=lnum+1;i<=len;i++) { SelectMin(i-1,&s1,&s2); //找出两个最小权值的下标放入s1和s2中 for(i = lnum+1;i <= len;i++) { SelectMin(i-1,&s1,&s2); //找出两个最小权值的下标放入s1,s1 huffTree[s1].parent = i; //将找出的两棵权值最小的字数合并为一颗子树 huffTree[s2].parent = i; //结点s1,s2的父亲设为i huffTree[i].leftchild = s1; //i的左右孩子为s1,s2 huffTree[i].rightchild = s2; huffTree[i].weight = huffTree[s1].weight + huffTree[s2].weight; //i的权值等于s1,s2权值和 } } } //销毁huffman树 void HuffMan::Destroy() { len = 0; lnum = 0; delete []huffTree; delete []huffCode; } //huffman编码 void HuffMan::Coding() { char *cd; int i,c,f,start; //求n个叶结点的huffman编码 cd = new char[lnum]; //分配求编码的工作空间 cd[lnum-1] = '