霍夫曼树
在数据结构与算法中,人们把最小带权路径长度的二叉树称为霍夫曼树或者最优二叉树。
霍夫曼算法
对应于霍夫曼树的算法也叫做霍夫曼算法:
(1)设给定的一组权值为{W1,W2,W3,……Wn},据此生成森林F={T1,T2,T3,……Tn},F 中的每棵二叉树只有一个带权为Wi的根节点(i=1,2,……n)。
(2)在F中选取两棵根节点的权值最小和次小的二叉树作为左右构造一棵新的二叉树,新二叉树根节点的权值为其左、右子树根节点的权值之和。
(3)在F中删除这两棵最小和次小的二叉树,同时将新生成的二叉树并入森林中。
(4)重复(2)(3)过程直到F中只有一棵二叉树为止。
霍夫曼树的应用非常广,在不同的应用中叶子节点的权值可以作不同的解释。霍夫曼树应用于信息编码中,权值可以看成某个符号出现的频率;应用到判定过程中,权值可以看成某类数据出现的频率;应用到排序过程中,权值可以看成是已排好次序而等待合并的序列长度等。
用霍夫曼算法实现文件压缩
具体到文件压缩的应用中,霍夫曼树叶子节点的权值可以看作是文件中某个字节的数据出现的频率。读取文件后根据文件中字节的出现频率构造霍夫曼树。
//统计字节数据出现的频率 void CHuffmanSys::GenFreqPair( LPVOID lpByte, UINT nSize ) { for( UINT i = 0; i < nSize; i++ ) { Item[*((PBYTE)lpByte)].AddCount(); lpByte = (LPVOID)((PBYTE)lpByte + 1); } }
构造霍夫曼树:
void CHuffmanSys::BuildSortList() { for( UINT i = 0; i < 256; i++ ) { if( 0 != Item[i].GetCount() ) this->InsertNodeInSort( Item[i] ); } } void CHuffmanSys::InsertNodeInSort( HuffmanNode NewNode ) { Node<HuffmanNode>* pHead = this->HuffList.pList; Node<HuffmanNode>* pTail = this->HuffList.pTail; if( !pTail ) HuffList.InsertNullList( NewNode ); else if( NewNode.GetCount() < pHead->Item.GetCount() ) HuffList.InsertInHead( NewNode ); else if( NewNode.GetCount() >= pTail->Item.GetCount() ) HuffList.InsertInTail( NewNode ); else { while( pHead != pTail ) { if( NewNode.GetCount() < pHead->Item.GetCount() ) break; pHead = pHead->pNext; } HuffList.InsertInIntl( pHead, NewNode ); } } HuffmanNode* CHuffmanSys::BuildHuffTree() { this->BuildSortList(); Node<HuffmanNode>* p = HuffList.pList; while(p->pNext) { HuffmanNode node( &p->Item, &p->pNext->Item ); InsertNodeInSort( node ); p = p->pNext->pNext; } this->root = &p->Item; return root; }
在进行霍夫曼编码时,根据根节点到叶子节点的路径,获得叶子节点(比特值)的编码值。对向左子节点和右子节点分别给二进制‘0’和‘1’,由于叶子节点的路径长度不同
因此形成的 编码的比特长度也不一样,这就要求任意一字节的编码都不能为另一比特编码的前缀,这种码称之为“前缀码”。编码的过程如下:
void CHuffmanSys::GenHuffCoding( HuffmanNode* root, CString code ) { if( root->IsLeaf() ) { this->CodingInBinStr[root->GetValue()] = code; } else { GenHuffCoding( root->pLeft, code+_T('0') ); GenHuffCoding( root->pRight, code+_T('1') ); } }
解码过程
对于编码后生成的新文件,需要记录霍夫曼树的结构,因此会对生成的编码文件写入一个霍夫曼 编码文件头。在解码时,可以读取文件头生成霍夫曼树,然后根据霍夫曼树进行解码。
解码过程:
PBYTE CHuffmanSys::HuffDecoding ( CString szText, UINT uStrLen ) { UINT nByteCount = 0; PBYTE TempByte = new BYTE[DEFAULT_PAGE_SIZE] ; HuffmanNode* p = root ; for ( UINT i = 0; i < uStrLen ; ) { p = root ; while ( !(p->IsLeaf()) && i < uStrLen ) { if ( szText.GetAt(i++) == '0' ) p = p->pLeft ; else p = p->pRight ; } TempByte[nByteCount++] = p->GetValue() ; } return TempByte ; }
VC实现霍夫曼压缩小程序:
【源代码下载】