一、Huffman树
霍夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,在众多领域中有着广泛的应用,尤其是信息编码。Huffman树的主要用处就是对数据进行编码,得到0-1码流,以实现快速传输,此编码也称为Huffman编码。本文主要给出了Huffman树的创建过程及相应的算法实现。
树的权路径长度是树上全部结点到树根的权路径长度之和。例如有如下一棵树,结点B、E和K的路径长度是1,C、D、F和G的路径长度是2,H和I的路径长度是3,J的路径长度是4,假设每个结点的权均是1,则该树的权路径长度为
WPL = 3×1+4×2+2×3+1×4 = 21
现在考虑二叉树,给每个树叶结点都赋予权值,则树叶的权值乘以路径长度就得到了每个树叶结点的权路径长度,所有树叶结点的权路径长度之和就是该二叉树的权路径长度。由于现在考虑的是有限结点的二叉树,因此由这些树叶结点所组成的全部二叉树中,一定存在一个权路径长度为最小的一棵,此树即为Huffman树。
例如:如下图给出的三个叶子结点的权分别是1,2和3的二叉树:
每一棵二叉树的权路径长度分别为:
图(a):WPL = 1×2+2×2+3×2 = 12
图(b):WPL = 3×1+1×2+2×2 = 9
图(c):WPL = 2×1+1×2+3×2 = 10
图(d):WPL = 1×1+2×2+3×2 = 11
由此可以看出图(2)中的二叉树对应的WPL值最小,其为Huffman树。
二、Huffman树的创建过程示例
给定一组数据:E, A, B, C, B, A, E, D, B, C, E, E, E, D, C, E, B, A, B, C,根据此数据创建一棵Huffman树。
构建Huffman树的过程如下:
Step 1: 统计每个元素出现的频数(按照字母出现的次序统计):
Step 2:以这些频数作为每个数据的权,创建Huffman树。
**1)**找出最小的二个权,生成一个二叉树(左小右大),二个权之和5作为二叉树的树根,把权值2和3从权序列这个删除,将5加入,得到新的权:
**2)**找出当前权中最小的两个权,继续生成一棵二叉树(遇到权值相同,则单个权值优先):
**3)**找出当前权中最小的两个权,继续生成一棵二叉树:
**4)**找出当前权中最小的两个权,继续生成一棵二叉树:
至此,就得到了一棵Huffman树,该Huffman树的权路径长度为:
WPL = 6×2+4×2+5×2+2×3+3×3 = 45.
三、Huffman树的创建流程
前面给出了Huffman树的创建过程,为了方便实现创建Huffman树,引入了结构体数组,用来存储Huffman树结点信息,并用结构数组存储Huffman树全部的结点。则前述的Huffman树创建过程可以用数组实现如下:
准备工作:生成一个结构体数组,存储全部权,数组元素个数为权个数的2倍减1。并将每一个权对应结点的双亲、左子树、右子树的序号置为-1:
Step 1:找到权值(有背景色)最小的两个3和2相加得到5,存到下标为5的元素中,并将下标5的左子树的下标赋值为4,右子树下标赋值为1,将下标4和1中的双亲下标赋值为5.
Step 2:在剩下的权值(有背景色)中找到权值最小的两个5和4相加得到9,存到下标为6的元素中,并将下标6的左子树的下标赋值为3,右子树下标赋值为2,将下标3和2中的双亲下标赋值为6.
Step 3:在剩下的权值(有背景色)中找到权值最小的两个5和6相加得到11,存到下标为7的元素中,并将下标7的左子树的下标赋值为5,右子树下标赋值为0,将下标5和0中的双亲下标赋值为7.
Step 4:在剩下的权值(有背景色)中找到权值最小的两个9和11相加得到20,存到下标为8的元素中,并将下标8的左子树的下标赋值为6,右子树下标赋值为7,将下标6和7中的双亲下标赋值为8.
四、创建Huffman树算法的C程序
1.存储Huffman树结点的结构体
typedef struct HTree
{
char cChar;
int Weight;
int Parent;
int LChild;
int RChild;
}HuffmanTree;
2.创建Huffman树的算法之C程序
//存储字符及字符出现次数(权值)的结构体
typedef struct charStat
{
char c;
int num;
}CharStat;
/*******************************************************************************
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
功能:创建Huffman树
输入:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
codeNum:不同字符的个数
输出:TreeNode:Huffman树的结点
返回值:无
********************************************************************************/
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
{
int i, j, k;
int w1, w2, p1, p2;
int nodeNum;
nodeNum = 2 * codeNum - 1;
for( i=0; i<nodeNum; i++ )//initial HuffmanTree
{
if( i < codeNum )
{
TreeNode[i].cChar = cStat[i].c;
TreeNode[i].Weight = cStat[i].num;
}
else
{
TreeNode[i].cChar = '\0';
TreeNode[i].Weight = 0;
}
TreeNode[i].LChild = BLANKFLAG;
TreeNode[i].RChild = BLANKFLAG;
TreeNode[i].Parent = BLANKFLAG;
}
//找最小的两个权生成一棵子树,存储到第一个非叶子结点,依此,直到只剩下一个结点
for( j=codeNum; j<nodeNum; j++ )
{
w1 = 0xFFFFFFF;
w2 = w1;
p1 = p2 = BLANKFLAG;
for( k=0; k<j; k++ )
{
if( TreeNode[k].Weight < w1 && TreeNode[k].Parent == BLANKFLAG )
{
w1 = TreeNode[k].Weight;
p1 = k;
}
}
for( k=0; k<j; k++ )
{
if( TreeNode[k].Weight < w2 && k != p1 && TreeNode[k].Parent == BLANKFLAG )
{
w2 = TreeNode[k].Weight;
p2 = k;
}
}
TreeNode[j].Weight = w1 + w2;
TreeNode[j].LChild = p1;
TreeNode[j].RChild = p2;
TreeNode[p1].Parent = j;
TreeNode[p2].Parent = j;
}
}//HTreeCreation
3.完整的测试代码
从键盘读入一个字符串,统计每个字符出现的次数,并据此创建Huffman树。
#include"stdio.h"
#include"string.h"
#define MAX_NODE 100
#define BLANKFLAG -1
//存储从键盘或者文件读入的字符串。
typedef struct charLinkList
{
char cChar;
struct charLinkList *pNext;
}CharLinkList;
//存储字符及字符出现的次数(权值)
typedef struct charStat
{
char c;
int num;
}CharStat;
//Huffman树的结点信息,其中前n个为叶子结点
typedef struct HTree
{
char cChar;
int Weight;
int Parent;
int LChild;
int RChild;
}HuffmanTree;
//获取字符串,并存储到以str为表头的单链表中
void getStr( CharLinkList &str, int &cNum );
//统计每个字符及出现的次数
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum );
//生成Huffman树
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum );
int main()
{
int i;
CharLinkList str, *p;
CharStat *cStat;
int cTotalNum, codeNum;
//从键盘获取待编码的字符串
printf( "Please input a string which need to be encoded:\n" );
getStr( str, cTotalNum );
//统计待编码字符串中不同字符及其出现的次数
cStat = new CharStat[ cTotalNum ];
cStatInf( str, cTotalNum, cStat, codeNum );
//向屏幕输出待编码字符串中的字符信息
printf( "the code infomation in string:\n" );
printf( "char : num\n" );
for( i=0; cStat[i].c != '\0'; i++ )
{
if( cStat[i].c == ' ' )
printf( "空格 : %d\n", cStat[i].num );
else
printf( "%3c : %d\n", cStat[i].c, cStat[i].num );
}
//根据待编码字符串中不同字符的信息创建Huffman树
//Huffman树的结点存储在结构体数组treeNode中
HuffmanTree *treeNode;
int nodeNum = 2 * codeNum - 1;
treeNode = new HuffmanTree[nodeNum];
HTreeCreation( cStat, treeNode, codeNum );
//向屏幕输出Huffman树结点信息,包括 序号、字符、权、双亲序号、左孩子序号、右孩子序号
printf( "Huffman Tree's node infomation:\n" );
printf( "%s%s%4c%4c%4c%4c\n", "num ", "Code", 'W', 'P', 'L', 'R' );
for( i=0; i<nodeNum; i++ )
{
printf( "%3d%5c%4d%4d%4d%4d\n", i, treeNode[i].cChar, treeNode[i].Weight, treeNode[i].Parent, treeNode[i].LChild, treeNode[i].RChild );
}
return 0;
}
/*******************************************************************************
void getStr( CharLinkList &str, int &cTotalNum )
功能:从键盘读入一个待编码的字符串,回车结束输入
输入:str:存储字符串的首地址,单链表
输出:cTotalNum:字符串中全部字符的个数
返回值:无
********************************************************************************/
void getStr( CharLinkList &str, int &cTotalNum )
{
CharLinkList *node, *p;
char ch;
cTotalNum = 0;
p = &str;
//每读入一个字符,即刻存储到一个单链表里面
while( 1 )//尾插入法创建单链表
{
ch = getchar();
if( ch == '\n' )
break;
cTotalNum++;
node = new CharLinkList;
node->cChar = ch;
node->pNext = NULL;
p->pNext = node;
p = node;
}
}//getStr
/*******************************************************************************
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum )
功能:统计每个字符及出现的次数
输入:str:引用,存储字符串的首地址
cTotalNum:字符串中全部字符的个数
输出:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
codeNum:不同字符的个数
返回值:无
********************************************************************************/
void cStatInf( CharLinkList str, int cTotalNum, CharStat *cStat, int &codeNum )
{
int i, j;
CharLinkList *p;
int loc; //记录全新字符存储的位置号
int newFlag; //新字符标记,如果是第一次出现,则该标志置为1,否则为0
p = str.pNext;
for( i=0; i<cTotalNum; i++ )
{
cStat[i].c = '\0';
cStat[i].num = 0;
}
loc = 0;
//从第一个字符开始统计不同的字符出现的次数
while( p != NULL )
{
newFlag = 1;//每读一个字符,都假设是第一次出现
//获取一个新位置上的字符,如果在此位置之前已经出现了,则计数器自加后结束本轮的查新
for( j=0; cStat[j].c != '\0'; j++ )
{
if( cStat[j].c == p->cChar )
{
newFlag = 0;
cStat[j].num++;
break;
}
}
//如果获取的新位置上的字符,在此位置之前没有出现,则存放到第一个空白位置
if( newFlag == 1 )
{
cStat[loc].c = p->cChar;
cStat[loc].num++;
loc++;
}
p = p->pNext;
}
codeNum = loc++;
}//cStatInf
/*******************************************************************************
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
功能:创建Huffman树
输入:cStat:存储统计结果的数组,每个元素包括2项,分别是字符、次数
codeNum:不同字符的个数
输出:TreeNode:Huffman树的结点
返回值:无
********************************************************************************/
void HTreeCreation( CharStat *cStat, HuffmanTree *TreeNode, int codeNum )
{
int i, j, k;
int w1, w2, p1, p2;
int nodeNum;
nodeNum = 2 * codeNum - 1;
for( i=0; i<nodeNum; i++ )//initial HuffmanTree
{
if( i < codeNum )
{
TreeNode[i].cChar = cStat[i].c;
TreeNode[i].Weight = cStat[i].num;
}
else
{
TreeNode[i].cChar = '\0';
TreeNode[i].Weight = 0;
}
TreeNode[i].LChild = BLANKFLAG;
TreeNode[i].RChild = BLANKFLAG;
TreeNode[i].Parent = BLANKFLAG;
}
//找最小的两个权生成一棵子树,存储到第一个非叶子结点,依此,直到只剩下一个结点
for( j=codeNum; j<nodeNum; j++ )
{
w1 = 0xFFFFFFF;
w2 = w1;
p1 = p2 = BLANKFLAG;
for( k=0; k<j; k++ )
{
if( TreeNode[k].Weight < w1 && TreeNode[k].Parent == BLANKFLAG )
{
w1 = TreeNode[k].Weight;
p1 = k;
}
}
for( k=0; k<j; k++ )
{
if( TreeNode[k].Weight < w2 && k != p1 && TreeNode[k].Parent == BLANKFLAG )
{
w2 = TreeNode[k].Weight;
p2 = k;
}
}
TreeNode[j].Weight = w1 + w2;
TreeNode[j].LChild = p1;
TreeNode[j].RChild = p2;
TreeNode[p1].Parent = j;
TreeNode[p2].Parent = j;
}
}//HTreeCreation
4.测试结果