哈夫曼树,又称最优树,是带权路径最小的树。
基本概念:
节点间的路径长度:两个节点间所包含的边的数目。
树的路径长度:从根到树中任意节点的路径长度之和。
权:将节点赋予一定的量值,该量值成为权。
树的带权路径长度:树中所有叶子结点的带权路径长度。
哈夫曼算法:给定一个保存权值的数组,求最优树的算法。对于此权值数组找出其中最小的权值和第二小的权值,用这两个权值创建树,并把这两个权值相加所得作为一个新权值放入到原数组中(注意:此时数组中已经去掉了刚才用过的权值),重复以上操作即可建立最优树。
哈弗曼编码和解码的优点不再赘述。
哈夫曼树的实现
1.建立结构体,比较简单
typedef struct Tree{
struct Tree *left;
struct Tree *right;
int data;
}Tree;
2.利用权值数组创建哈夫曼树(代码注释已相对清晰)
Tree *create(int *a,int n){//对数组 a 进行实现哈夫曼树 a 中存放的为权值, n 为数组的长度
Tree *tree;
Tree **b;
int i,j;
b = malloc(sizeof(Tree)*n);//动态一维数组的申请来保存权值
for(i = 0; i < n; i++){
b[i] = malloc(sizeof(Tree));
b[i]->data = a[i];
b[i]->left = b[i]->right = NULL;
}
//创建哈夫曼树
for(i = 1; i < n; i++){
int small1 = -1,small2;//small1指向权值最小,small2是第二小,其初始指向分别是数组的前两个元素
//注意前两个元素并不一定是最小的和第二小的
//下面一个 for 循环是让small1指向第一个权值,small2指向第二个权值
for(j = 0; j < n; j++){
if(b[j] != NULL && small1 == -1){
small1 = j;
continue;
}
if(b[j] != NULL){
small2 = j;
break;
}
}
//接下来就是对数组剩下的权值逐个与small1、small2比较,找出最小与第二小的权值
for(j = small2; j < n; j++){
if(b[j] != NULL){
if(b[j]->data < b[small1]->data){
small2 = small1;
small1 = j;
}
else if(b[small2]->data > b[j]->data){
small2 = j;
}
}
}
//由两个最小权值建立新树,tree 指向根节点
tree = malloc(sizeof(Tree));
tree->data = b[small1]->data + b[small2]->data;
tree->left = b[small1];
tree->right = b[small2];
//以下两步是用于重复执行
b[small1] = tree;
b[small2] = NULL;
}
free(b);
return tree;
}
3.打印哈夫曼树
//打印哈夫曼树
void print(Tree *tree){
if(tree){
printf("%d ",tree->data);
if(tree->left && tree->right){
printf("(");
print(tree->left);
if(tree->right)
printf(",");
print(tree->right);
printf(")");
}
}
}
4.获取哈夫曼树的带权路径长度
//获得哈夫曼树的带权路径长度
int getWeight(Tree *tree,int len){
if(!tree)
return 0;
if(!tree->left && !tree->right)//访问到叶子结点
return tree->data * len;
return getWeight(tree->left, len + 1) + getWeight(tree->right,len + 1);//访问到非叶子结点
}
5.下面便是哈夫曼编码与解码,思路也较为简单
//哈夫曼编码
void getCoding(Tree *tree,int len){
if(!tree)
return;
static int a[20]; //定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一
int i;
if(!tree->left && !tree->right){
printf(" %d 的哈夫曼编码为:",tree->data);
for(i = 0; i < len; i++)
printf("%d",a[i]);
printf("\n");
}
else{//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a ,的对应元素中,向下深入一层时len值增1
a[len] = 0;
getCoding(tree->left, len + 1);
a[len] = 1;
getCoding(tree->right, len + 1);
}
}
6.哈夫曼解码,比较容易实现
节点与左子节点之间记为 0 ,节点与右节点之间记为 1 ,如图
//哈夫曼解码
void Decoding(Tree *tree){
printf("请输入要解码的字符串\n");
char ch[100];//输入的待解码的字符串
gets(ch);
int i;
int num[100];//用于保存字符串对应的0 1 编码对应的节点
Tree *cur;
for(i = 0; i < strlen(ch); i++){
if(ch[i] == '0')
num[i] = 0;
else
num[i] = 1;
}
if(tree){
i = 0;
while(i < strlen(ch)){
cur = tree;
while(cur->left && cur->right){
if(num[i] == 0)
cur = cur->left;
else
cur = cur->right;
i++;
}
printf("%d",cur->data);
}
}
}
完整代码如下
#include
#include
#include
typedef struct Tree{
struct Tree *left;
struct Tree *right;
int data;
}Tree;
Tree *create(int *a,int n){//对数组 a 进行实现哈夫曼树 a 中存放的为权值, n 为数组的长度
Tree *tree;
Tree **b;
int i,j;
b = malloc(sizeof(Tree)*n);//动态一维数组的申请来保存权值
for(i = 0; i < n; i++){
b[i] = malloc(sizeof(Tree));
b[i]->data = a[i];
b[i]->left = b[i]->right = NULL;
}
//创建哈夫曼树
for(i = 1; i < n; i++){
int small1 = -1,small2;//small1指向权值最小,small2是第二小,其初始指向分别是数组的前两个元素
//注意前两个元素并不一定是最小的和第二小的
//下面一个 for 循环是让small1指向第一个权值,small2指向第二个权值
for(j = 0; j < n; j++){
if(b[j] != NULL && small1 == -1){
small1 = j;
continue;
}
if(b[j] != NULL){
small2 = j;
break;
}
}
//接下来就是对数组剩下的权值逐个与small1、small2比较,找出最小与第二小的权值
for(j = small2; j < n; j++){
if(b[j] != NULL){
if(b[j]->data < b[small1]->data){
small2 = small1;
small1 = j;
}
else if(b[small2]->data > b[j]->data){
small2 = j;
}
}
}
//由两个最小权值建立新树,tree 指向根节点
tree = malloc(sizeof(Tree));
tree->data = b[small1]->data + b[small2]->data;
tree->left = b[small1];
tree->right = b[small2];
//以下两步是用于重复执行
b[small1] = tree;
b[small2] = NULL;
}
free(b);
return tree;
}
//打印哈夫曼树
void print(Tree *tree){
if(tree){
printf("%d ",tree->data);
if(tree->left && tree->right){
printf("(");
print(tree->left);
if(tree->right)
printf(",");
print(tree->right);
printf(")");
}
}
}
//获得哈夫曼树的带权路径长度
int getWeight(Tree *tree,int len){
if(!tree)
return 0;
if(!tree->left && !tree->right)//访问到叶子结点
return tree->data * len;
return getWeight(tree->left, len + 1) + getWeight(tree->right,len + 1);//访问到非叶子结点
}
//哈夫曼编码
void getCoding(Tree *tree,int len){
if(!tree)
return;
static int a[20]; //定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一
int i;
if(!tree->left && !tree->right){
printf(" %d 的哈夫曼编码为:",tree->data);
for(i = 0; i < len; i++)
printf("%d",a[i]);
printf("\n");
}
else{//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a ,的对应元素中,向下深入一层时len值增1
a[len] = 0;
getCoding(tree->left, len + 1);
a[len] = 1;
getCoding(tree->right, len + 1);
}
}
//哈夫曼解码
void Decoding(Tree *tree){
printf("请输入要解码的字符串\n");
char ch[100];//输入的待解码的字符串
gets(ch);
int i;
int num[100];//用于保存字符串对应的0 1 编码对应的节点
Tree *cur;
for(i = 0; i < strlen(ch); i++){
if(ch[i] == '0')
num[i] = 0;
else
num[i] = 1;
}
if(tree){
i = 0;
while(i < strlen(ch)){
cur = tree;
while(cur->left && cur->right){
if(num[i] == 0)
cur = cur->left;
else
cur = cur->right;
i++;
}
printf("%d",cur->data);
}
}
}
int main(int argc, char *argv[]) {
int a[4] = {2,6,7,3};
Tree *tree = create(a,4);
print(tree);
printf("\n哈夫曼树的权值为:");
printf("%d\n",getWeight(tree,0));
getCoding(tree,0);
printf("解码时请参照上方编码\n");
Decoding(tree);
}