采用霍夫曼编码时有两个问题值得注意:
①霍夫曼码没有错误保护功能,在译码时,如果码串中没有错误,那么就能一个接一个地正确译出代码。但如果码串中有错误,哪仅是1位出现错误,不但这个码本身译错,更糟糕的是一错一大串,全乱了套,这种现象称为错误传播(error propagation)。计算机对这种错误也无能为力,说不出错在哪里,更谈不上去纠正它。
②霍夫曼码是可变长度码,因此很难随意查找或调用压缩文件中间的内容,然后再译码,这就需要在存储代码之前加以考虑。尽管如此,霍夫曼码还是得到广泛应用。
/* 霍夫曼编码模型:思想是压缩数据出现概率高的用短编码,出现概率低的用长编码,且每个字符编码都不一样。 压缩数据单个字符出现的概率抽象为叶子节点的权值,huffman树叶子节点到根节点的编码(是父节点左子节点那么填0,否则填1) 作为字符的唯一编码. 实现时候需要注意的规则: 1)最左的放置在左边,作为父节点的左节点。 2)每次都从没有设置父节点的所用节点中(叶子和分支节点一样对待),从数组小下标到大下标优先顺序遍历。 3)当前搜寻的次数i + n作为新生成的分支节点的数组下标。 实现的过程和具体算法思想: 两个数据结构: 一个是huffman树节点结构体,一个是从霍夫曼树叶子节点编码的结构体。 两个处理过程: 1)建立huffman树: 基本思想是:对于没有设置父节点的节点集选出最小的两个,最小的放置在左边,次小的放置在右边 设置好父节点和左右子节点关系,方便获得霍夫曼编码。 2) 从huffman树得到叶子节点的huffman编码: 基本思想:对于建立好的Huffman树的每个叶子节点,由编码的数组的末端也是从叶子节点最底端,往上遍历 如果是父节点的左节点那么用编码数组填1,如果是父节点的右节点那么编码数组填0,一直往上追溯到根节点。 */ // 以下是实现的代码,在win32编译通过。 #include "stdafx.h" #define MAXCODELEN 7 #define MAXWEIGHT 10000 struct tagHuffmanNode { int m_nWeight; int m_nParent; int m_nLChild; int m_nRChild; }; struct tagHuffmanCode { int m_nWeight; int m_nStart; int m_nBit[MAXCODELEN]; }; void Huffman(int w[], int n, tagHuffmanNode ht[], tagHuffmanCode hc[]) { int nTotalCount = 2 * n - 1; // 初始化填充好ht的所有权值,包括叶子节点和分支节点 for(int i = 0; i < nTotalCount; i++) { if( i < n) { ht[i].m_nWeight = w[i]; } else { ht[i].m_nWeight = 0; } ht[i].m_nParent = 0; ht[i].m_nLChild = ht[i].m_nRChild = -1; } // 构造一颗huffman树,设置n-1个分支节点(非叶子), // 基本思想是:对于没有设置父节点的节点集选出最小的两个,最小的放置在左边,次小的放置在右边 // 设置好父节点和左右子节点关系,方便获得霍夫曼编码 int nCurMinWeight,nCurSecondMinWeight; int nCurLeftChild, nCurRightChild; for( int i = 0; i < n-1; i++) { nCurMinWeight = nCurSecondMinWeight = MAXWEIGHT; nCurLeftChild = nCurRightChild = 0; // 确定一个分支节点,需要对n + i个节点进行筛选 for( int j = 0; j < n + i; j++) { if( ht[j].m_nWeight < nCurMinWeight && ht[j].m_nParent == 0) { nCurSecondMinWeight = nCurMinWeight; nCurRightChild = nCurLeftChild; nCurMinWeight = ht[j].m_nWeight; nCurLeftChild = j; } else if(ht[j].m_nWeight < nCurSecondMinWeight && ht[j].m_nParent == 0) { nCurSecondMinWeight = ht[j].m_nWeight; nCurRightChild = j; } } // 得到分支节点,设置节点关系 ht[n + i].m_nLChild = nCurLeftChild; ht[n + i].m_nRChild = nCurRightChild; ht[n + i].m_nWeight =nCurMinWeight + nCurSecondMinWeight; ht[nCurLeftChild].m_nParent = n + i; ht[nCurRightChild].m_nParent = n + i; } // 测试用 /*for(int i = 0; i < nTotalCount; i++) { printf("--------------------------------\n"); printf("ht[%d].m_nWeight: %d\n", i, ht[i].m_nWeight); printf("ht[%d].m_nParent: %d\n", i, ht[i].m_nParent); printf("ht[%d].m_nLChild: %d\n", i, ht[i].m_nLChild); printf("ht[%d].m_nRChild: %d\n", i, ht[i].m_nRChild); }*/ // 记录下来每个叶子节点的huffman编码 // 基本思想:对于建立好的Huffman树的每个叶子节点,由编码的数组的末端也是从叶子节点最底端,往上遍历 // 如果是父节点的左节点那么用编码数组填1,如果是父节点的右节点那么编码数组填0,一直往上追溯到根节点。 for(int k = 0; k < n; k++) { hc[k].m_nWeight = ht[k].m_nWeight; hc[k].m_nStart = n - 1;// start等于最大的值 int nChild = k; int nParent = ht[k].m_nParent; while(nParent != 0) { if(nChild == ht[nParent].m_nLChild) { hc[k].m_nBit[hc[k].m_nStart] = 0; } else if(nChild == ht[nParent].m_nRChild) { hc[k].m_nBit[hc[k].m_nStart] = 1; } hc[k].m_nStart--; nChild = nParent; nParent = ht[nChild].m_nParent; } // 因为递减了需要增加,得到正确的起始下标 hc[k].m_nStart++; } } int _tmain(int argc, _TCHAR* argv[]) { int nDataNum = 7; int nWeigt[] = {4, 2, 6, 8, 3, 2, 1}; const int nMaxLen = 2 * nDataNum - 1; tagHuffmanNode *ht = new tagHuffmanNode[nMaxLen]; tagHuffmanCode *hc = new tagHuffmanCode[nDataNum]; Huffman(nWeigt, nDataNum, ht, hc); for(int i = 0; i < 7; i++) { printf("index: %d, weight: %d, hc[%d].m_nBit: ", i, hc[i].m_nWeight, i); for(int j = hc[i].m_nStart; j < 7; j++) { printf("%d", hc[i].m_nBit[j]); } printf("\n"); } delete []ht; delete []hc; while(1); return 0; }