[问题描述]
设某编码系统共有 n 个字符,使用频率分别为{ w1, w2, …, wn},设计一个不等长的 编码方案,使得该编码系统的空间效率最好。
[基本要求]
① 设计数据结构;
② 设计编码算法;
③ 分析时间复杂度和空间复杂度。
[实现提示]
利用哈夫曼编码树求得最佳的编码方案。利用哈夫曼算法建立哈夫曼树,在哈夫曼树中, 设左分支为 0,右分支为 1,从根结点出发,遍历整棵哈夫曼树,求得各叶子结点所表示字 符的哈夫曼编码。
[思维扩展]
对于采用哈夫曼编码树进行的编码,如何设计解码算法?
假设输入了n个字符作为哈夫曼树的叶子结点,那么哈夫曼树中的总结点个数就是2n-1个,根据输入的每个字符对应的权值大小(根据权值大的字符在上面,权值小的字符在下面)建立哈夫曼树。
1)数据结构:
typedef struct
{
float weight;//哈夫曼树的权值
int parent, lchild, rchild;//对应的父母节点,左孩子,右孩子节点
} hufmtree;//树节点
typedef struct{
char bits[n];//编码
int start;//起始点
float ch;//对应的权值
}Codetype;// 哈夫曼树编码
hufmtree tree[2 * n - 1];
Codetype code[n];
2)算法
1.创建哈夫曼树
void C_H(){
int i, j, p1, p2;
float small1, small2, f;
for (i = 0; i < m; i++)
{ //初始化数组
tree[i].parent = 0;
tree[i].lchild = 0;
tree[i].rchild = 0;
tree[i].weight = 0.0;
}
for (i = 0; i < n; i++)
{ //读入前n个节点的权值
scanf("%f",&f);
tree[i].weight = f;
}
for (i = n; i < m; i++)
{ // n-1次合并.产生n-1个新节点
p1 = p2 = 0;
small1 = small2 = max;
for (j = 0; j <= i - 1; j++) //选两权值最小根节点
{
if (tree[j].parent != 0){// j节点加入树中
continue;
}
else{
if (tree[j].weight < small1)
{ //查找最小权.用p1记录下标
small2 = small1;
small1 = tree[j].weight;
p2 = p1;
p1 = j;
}
else if (tree[j].weight < small2)
{ //查找次小权.用p2记录其下标
small2 = tree[j].weight;
p2 = j;
}
}
}
tree[p1].parent = i;
tree[p2].parent = i;
tree[i].weight = tree[p1].weight + tree[p2].weight;
tree[i].lchild = p1;
tree[i].rchild = p2;
}
}
2.形成编码:
void H_c(){
int i,j,p;
for ( i = 0; i < n; i++)
{
code[i].start=n-1;
code[i].ch=tree[i].weight;
j=i;
p=tree[i].parent;
while(p!=0){
if(tree[p].lchild==j) code[i].bits[code[i].start]='0';
else code[i].bits[code[i].start]='1';
code[i].start--;
j=p;
p=tree[p].parent;
}
}
}
3).主函数
int main(){
C_H();
int i,j;
H_c();
for ( i = 0; i < n; i++)
{
printf("%.1f ",code[i].ch);
for ( j = 0; j <n ; j++)
{
printf("%c",code[i].bits[j]);
}
printf(" ");
}
return 0;
}
4).运行结果
说明:第一行为输入的五个权值
第二行为每个权值对应的编码
思考:
哈夫曼树每个结点可能存在左右孩子结点,左孩子的编码为0,右孩子的编码为1,通过从根节点遍历哈夫曼树,对每个叶子节点中对应的字符进行编码。解码时通过判断二进制数中的0或1查找叶子节点中的字符。