哈夫曼树又叫最优二叉树,即叶子结点带权路径长度之和(WPL)最小。
关于哈夫曼树有在以下链接中的作者解释的很详细,不太清楚的同学可以去看一下。
https://www.cnblogs.com/zhangming-blog/p/5395950.html
下面我想补充下下哈夫曼编码,哈夫曼编码又称霍夫曼编码,或者最优编码。哈夫曼编码主要有以下特点:
一 : 不等长编码;
二: 最优编码;
三: 无歧义编码,即任意字符的编码都不是另一字符的前缀;
四: 所有应编码的字符位于叶子结点。
哈夫曼编码采用的是二进制编码方式,父结点与左儿子结点之间的路径编为0,与右儿子结点之间的路径编为一。举个例子来说明一下。
在这个例子中,1,2,3,4为待编码的字符.
1的编码序列为: 110
2的编码序列为: 111
3的编码序列为: 10
4的编码序列为: 0
可以看出,各字符的编码序列不尽相同,权重越大编码序列越短,即路径越短。
WPL = 1*3 + 2*3 + 3*2 + 4*1 = 19
需要注意的是:哈夫曼树一定是最优编码,但最优编码不一定需要通过哈夫曼树实现。这一点哈夫曼先生在他的论文也有说明,有兴趣的同学可以验证下。
下面是哈夫曼树的结构:
struct Node
{
int weight; //权重
int *code; //编码
int codeLen; //编码长度
Huffman left; //左孩子结点
Huffman right; //右孩子结点
};
以下哈夫曼树的创建代码:
Huffman Creat_Huffman(int N)
{
int i, n;
MinHeap H;
Huffman T;
H = Creat_MinHeap(N); //创建最小堆
n = H->size;
for( i = 1; i < n; i++ )
{
T = (Huffman)malloc( sizeof( struct Node ) ); //创建一个根结点
T->left = Delete(H); //从最小堆中取权重最小的结点赋给左孩子结点
T->right = Delete(H); //同上
T->weight = T->left->weight + T->right->weight; //父节点的权重等于左右孩子结点权重之和
Insert( H, T ); //将父节点放入最小堆中
}
T = Delete(H); // T指向堆中的头结点也就是哈夫曼树的根结点
return T;
}
上面的代码中哈夫曼树的创建借助了最小堆来实现,对最小堆不太清楚的同学可以去看我的另一篇关于最小堆的博客。
此为链接:最小堆
下面是哈夫曼编码的代码实现:
void HuffmanCode( Huffman T, int len )
{
static int s[20]; //静态数组,存储数据编码
if( T )
{
if( !T->left && !T->right )
{
printf("结点权重为 %-3d 的编码是: ", T->weight);
T->code = (int *)malloc( len * sizeof( int ) ); //开辟空间存储叶子结点编码
for( int i = 0; i < len; i++)
{
T->code[i] = s[i]; //将字符的编码存入对应结点中
printf("%d", T->code[i]);
}
T->codeLen = len; //存储编码长度
printf("\n");
}
else
{
//递归遍历各结点的路径
s[len] = 0;
HuffmanCode( T->left, len+1 );
s[len] = 1;
HuffmanCode( T->right, len+1 );
}
}
}
以下为全代码实现:
#include<stdio.h>
#include<malloc.h>
#define ElementType Huffman
#define MaxSize 100 //堆堆的最大空间
typedef struct Node* Huffman;
typedef struct Heap* MinHeap;
struct Node
{
int weight; //权重
int *code; //编码
int codeLen; //编码长度
Huffman left; //左孩子结点
Huffman right; //右孩子结点
};
struct Heap
{
ElementType *element; //存储元素
int size; //元素的数量
int capacity; //堆的最大空间
};
//插入一个元素
void Insert(MinHeap H, Huffman e)
{
int i;
if( H->size == H->capacity ) //判断堆是否已满
{
printf("堆已满!");
return ;
}
i = ++H->size; //所存元素数量加1
for( ; i>=2 && H->element[i/2]->weight > e->weight; i /= 2) //当要插入的元素小于父节点时,父节点向下挪动,要插入的元素继续向上比较
H->element[i] = H->element[i/2];
H->element[i] = e;
}
//创建最小堆
MinHeap Creat_MinHeap(int n)
{
ElementType tmp;
int i;
MinHeap H = (MinHeap)malloc( sizeof(struct Heap) );
H->element = (ElementType*)malloc( (MaxSize+1) * sizeof(ElementType) );
H->size = 0;
H->capacity = MaxSize;
for( i = 1; i <= n; i++ ) //将元素按输入顺序放入数组中
{
tmp = (ElementType)malloc( sizeof( struct Node ) );
tmp->left = NULL;
tmp->right = NULL;
scanf("%d", &tmp->weight);
Insert( H, tmp );
}
return H;
}
//删除根结点并返回其值
ElementType Delete(MinHeap H)
{
int parent, child;
ElementType tmp, e;
if( H->size == 0)
{
printf("堆为空!");
return 0;
}
e = H->element[1];
tmp = H->element[H->size--];
for( parent = 1; parent*2 <= H->size; parent = child )
{
child = parent * 2;
if(child != H->size && H->element[child+1]->weight < H->element[child]->weight)
child++; //child指向左右儿子中较小的那个;
if( tmp->weight <= H->element[child]->weight ) //如果tmp小于等于左右儿子中较小的那个,结束循环
break;
else //否则,较小的儿子上移,tmp进入下一层
H->element[parent] = H->element[child];
}
H->element[parent] = tmp;
return e;
}
//先序遍历输出哈夫曼树
void Print( Huffman T )
{
if( T )
{
printf("%d ", T->weight);
Print( T->left );
Print( T->right );
}
}
Huffman Creat_Huffman(int N)
{
int i, n;
MinHeap H;
Huffman T;
H = Creat_MinHeap(N); //创建最小堆
n = H->size;
for( i = 1; i < n; i++ )
{
T = (Huffman)malloc( sizeof( struct Node ) );
T->left = Delete(H);
T->right = Delete(H);
T->weight = T->left->weight + T->right->weight;
Insert( H, T );
}
T = Delete(H);
return T;
}
//哈夫曼编码
void HuffmanCode( Huffman T, int len )
{
static int s[20]; //静态数组,存储数据编码
if( T )
{
if( !T->left && !T->right )
{
printf("结点权重为 %-3d 的编码是: ", T->weight);
T->code = (int *)malloc( len * sizeof( int ) ); //开辟空间存储叶子结点编码
for( int i = 0; i < len; i++)
{
T->code[i] = s[i]; //将字符的编码存入对应结点中
printf("%d", T->code[i]);
}
T->codeLen = len; //存储编码长度
printf("\n");
}
else
{
//递归遍历各结点的路径
s[len] = 0;
HuffmanCode( T->left, len+1 );
s[len] = 1;
HuffmanCode( T->right, len+1 );
}
}
}
//计算带权最优编码长度
int WPL( Huffman T )
{
if( !T->right && !T->left)
{
return T->weight*T->codeLen;
}
else
return ( WPL( T->left ) + WPL( T->right ) );
}
int main()
{
int N;
Huffman T;
scanf("%d", &N); //需要编码的字符数量
T = Creat_Huffman(N); //创建哈夫曼树
printf("\n哈夫曼树先序遍历结果为: ");
Print( T ); //先序遍历哈夫曼树
printf("\n");
HuffmanCode( T, 0 ); //对字符进行编码
printf("最优编码长度为: %d\n", WPL( T )); //输出带权路径之和
return 0;
}
以上即为所有代码,不懂之处,欢迎交流,不足之处,恳请指正。