设二叉树具有n个带权值的叶子节点,从根节点到叶子节点的路径长度与对应叶子节点权值的乘积之和叫做二叉树的“带权路径长度”。
对于一组带有权值的叶子节点,带权路径长度最小的二叉树叫做“最优二叉树”(例如哈夫曼树,哈夫曼树是最优二叉树,最优二叉树不一定是哈夫曼树)。
如何创建一颗哈夫曼树?
创建n个根节点,权值为{w1,w2,,,,wn},带到森林{T1,T2,,,,Tn};从森林中选取权值最小的两颗二叉树,合并为新的二叉树,新二叉树根节点的权值为两权值之和;将新二叉树加入森林,被归并的两颗二叉树不再看做是二叉树;重复选取合并操作,直至森林只有一颗二叉树,得到的二叉树就是哈夫曼树。哈夫曼树不一定唯一,但最小带权路径长度都相同。
只要权值个数(叶节点个数)严格大于1,哈夫曼树中便不存在度为1的节点,权值个数 (叶节点个数)为n,则哈夫曼树的节点个数为(2n-1)。
哈夫曼树对应的编码为哈夫曼编码,是一种最优前缀编码。
7.创建哈夫曼树的思路:
分析:含n个字符则哈夫曼树有(2n-1)个节点,动态开辟(2n-1)个节点的内存,用顺序存储结构存储。构造树时,从叶子节点往上走,识别字符或者解码时从上往下走,故节点要包含双亲,左右孩子下标和权值。
具体思路:
1)动态开辟所有节点的存储空间,初始化各叶节点和分支节点(已知各叶节点的权值,其他各项初始化为0,具体原因下面分析。)
2)构造哈夫曼树关键是逐步确定各分支节点的相关信息:求出最小的二叉树,据此设置当前各分支节点各成员的值。
3)开辟空指针数组。开辟临时存放单个编码的数组,从叶出发逆向寻根,每向上一步都将当前编码符记录到临时数组最后一个空位置,待到达根则临时数组中得到临时编码,后将该编码复制到指针数组的适当位置即可。
构建哈夫曼树,求哈夫曼编码代码:
typedef struct
{
unsigned int weight;
unsigned int parent,lchild,rchild;
} HTNode;
typedef HTNode *HuffmanTree;
typedef char* *HuffmanCode;//用于存放编码的数组
int minn(HuffmanTree &HT,int k)
{
int i = 0;
int min_weight = 0;
int min_index= 0;
//HT[i]->parent != 0说明该节点不能再用来做其他节点的孩子
while(HT[i].parent != 0)
{
++i;
}
//记下最小的权值及其对应于HT的下标
min_weight = HT[i].weight;
min_index = i;
//选出weight最小的元素后,将其parent置为-1,使得下一次比较选取时将其排除在外
HT[i].parent = -1;
return min_weight;
}
Status Select(HuffmanTree &HT,int k,int &min1,int &min2)
{
min1 = minn(HT,k);//从未用过的前k个数里选出一个最小的数
min2 = minn(HT,k);//从未用过的前k个数里选出一个最小的数(在minn的具体实现里会控制不再选取第一次选出的数)
return true;
}
//w为权值数组,HT为要创建的哈夫曼树,HC为存放单个编码的数组,n为叶子节点个数
Status HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC,int *w,int n)
{
if(n <= 1)
return ERROR;
int m = 2*n-1;//整棵哈夫曼树的节点个数
HT = (HTNode*)malloc((m+1)*sizeof(HTNode));//分配空间的时候多分配一个,从第一个开始存,0号不存
int i = 1;
//先初始化叶子,只知道权值
for(i = 1; i <= n; ++i,++w)
{
HT[i].weight = *w;
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
//初始化分支节点,各个信息赋值为0
for(i = n+1; i <= m; ++i,++p)
{
HT[i].weight = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
HT[i].parent = 0;
}
//哈夫曼树共有2n-1个节点,前n个节点是叶子节点,已经有信息了,剩下的节点从第n+1个开始放
for(i = n+1; i <= m; ++i)
{
Select(HT,i-1,min1,min2);
HT[min1].parent = i;
HT[min2].parent = i;
HT[i].weight = min1 + min2;
HT[i].lchild = min1;
HT[i].rchild = min2;
HT[i].parent = 0;
}
//共有n个节点,每个节点对应一个前缀编码
HC = (HuffmanCode*)malloc((n+1)*sizeof(char*));
char * cd = (char*)malloc(sizeof(char)*n);//存编码的临时空间,编码最长为(n-1)
cd[n-1] = '\0';
//逐个叶节点求其编码(HT中1-n是叶子节点)
for(int i = 1; i <= n; ++i)
{
int start = n-1;
int c;
unsigned int f;
//叶节点逆向求编码
for(f=HT[i].parent,c=i;f != 0; c = f,f = HT[f].parent)
{
if(HT[f].lchild == c)
cd[--start] = '0';
else
cd[--start] = '1';
}
//上一个编码用了n-start个空间,再分配n-start个空间
HC[i] = (char*)malloc(sizeof(char)*(n-start));
strcpy(HC[i],&cd[start]);
}
}
(Select函数!!!minn函数!!!构建哈夫曼树的思路!!!)