霍夫曼树及霍夫曼编码的C语言实现

从周五开始学习霍夫曼树,一直到今天终于完成,期间遇到了各种各样的棘手的问题,通过一遍遍在纸上分析每一步的具体状态得以解决。现在对学习霍夫曼树的过程加以记录

首先介绍霍夫曼树

霍夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有n个权值{w1,w2,…,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,…,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。

这里补充下树的带权路径长度的概念。树的带权路径长度指树中所有叶子节点到根节点的路径长度与该叶子节点权值的乘积之和,如果在一棵二叉树中共有n个叶子节点,用Wi表示第i个叶子节点的权值,Li表示第i个也叶子节点到根节点的路径长度,则该二叉树的带权路径长度 WPL=W1*L1 + W2*L2 + … Wn*Ln。

根据节点的个数以及权值的不同,赫夫曼树的形状也各不相同,赫夫曼树具有如下特性:

对于同一组权值,所能得到的霍夫曼树不一定是唯一的。

赫夫曼树的左右子树可以互换,因为这并不影响树的带权路径长度。

带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。

权值越大的节点越靠近赫夫曼树的根节点,权值越小的节点越远离赫夫曼树的根节点。

赫夫曼树中只有叶子节点和度为2的节点,没有度为1的节点。

一棵有n个叶子节点的赫夫曼树共有2n-1个节点。

赫夫曼树的构建步骤如下:

1、将给定的n个权值看做n棵只有根节点(无左右孩子)的二叉树,组成一个集合HT,每棵树的权值为该节点的权值。

2、从集合HT中选出2棵权值最小的二叉树,组成一棵新的二叉树,其权值为这2棵二叉树的权值之和。

3、将步骤2中选出的2棵二叉树从集合HT中删去,同时将步骤2中新得到的二叉树加入到集合HT中。

4、重复步骤2和步骤3,直到集合HT中只含一棵树,这棵树便是赫夫曼树。

假如给定如下5个权值:

则按照以上步骤,可以构造出如下面左图所示的赫夫曼树,当然也可能构造出如下面右图所示的赫夫曼树,这并不是唯一的。

Huffman编码

赫夫曼树的应用十分广泛,比如众所周知的在通信电文中的应用。在等传送电文时,我们希望电文的总长尽可能短,因此可以对每个字符设计长度不等的编码,让电文中出现较多的字符采用尽可能短的编码。为了保证在译码时不出现歧义,我们可以采取如下图所示的编码方式:

即左分支编码为字符0,右分支编码为字符1,将从根节点到叶子节点的路径上分支字符组成的字符串作为叶子节点字符的编码,这便是赫夫曼编码。我们根据上面左图可以得到各叶子节点的赫夫曼编码如下:

权值为5的也自己节点的赫夫曼编码为:11

权值为4的也自己节点的赫夫曼编码为:10

权值为3的也自己节点的赫夫曼编码为:00

权值为2的也自己节点的赫夫曼编码为:011

权值为1的也自己节点的赫夫曼编码为:010

而对于上面右图,则可以得到各叶子节点的赫夫曼编码如下:
权值为5的也自己节点的赫夫曼编码为:00
权值为4的也自己节点的赫夫曼编码为:01
权值为3的也自己节点的赫夫曼编码为:10
权值为2的也自己节点的赫夫曼编码为:110
权值为1的也自己节点的赫夫曼编码为:111

下面给出C语言实现

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
typedef int Status;
/*定义霍夫曼树节点*/
typedef struct HTNode{
    int parent;/*记录双亲*/
    int Lchild;/*左右子树*/
    int Rchild;
    int Weight;/*记录权重*/
}HTNode;
typedef struct HTNode * HuffmanTree;
typedef char ** HuffmanCode;

/*在前k棵树种找到权重最小的树*/
int Min(HuffmanTree HT,int k){
    int i=0,min_weight=0,min=0;
    /*找出第一个双亲存在的节点,将其权值赋值给min_weight*/
    /*注意此处不能直接将HT[0].weight赋给min_weight,原因是如果HT[0].weight最小,那么在第一次构造二叉树时就会被选走,而后续的每一轮选择最小权值构造二叉树的比较
    还是先用HT[0].weight的值来进行判断,这样又会再次将其选走,从而产生逻辑上的错误。*/
    while(HT[i].parent!=-1)
       i++;
    min_weight=HT[i].Weight;
    min=i;
    for(i;i<k;i++){
        if(HT[i].Weight<min_weight&&HT[i].parent==-1){
            min_weight=HT[i].Weight;
            min=i;
        }
    }
    /*找到最小权重的树,将其双亲置为1*/
    /*!!!!!注意这的HT的下标!!!!!一晚上才找出这个小问题,别写成HT[i]!!!!!!*/
    HT[min].parent=1;
    return min;
}

/*从前k棵树中选出权重最小的两棵树,将其序号赋给min1和min2*/
Status SelectMin(HuffmanTree HT,int k,int &min1,int &min2){
    min1=Min(HT,k);
    min2=Min(HT,k);
    return OK;
} 

/*创建一课霍夫曼树,-1表示不存在*/
/*wet为一个记录权重的数组,类型为int*/
HuffmanTree CreateHuffmanTree(HuffmanTree HT,int *wet,int n){
    int i=0;
    int total=2*n-1;/*有n个数据需要编码,即有n个叶子节点,也就有n-1个度为2的节点,总节点数为n+n-1=2*n-1*/
    /*初始状态下,前n个节点的双亲,左右子树应该均为-1,权重为对应的权重*/
    /*用HT的前n个分量存储n棵树(由n个待编码的数据组成)的森林*/
    /*申请total个int组成的动态数组*/
    HT=(HuffmanTree)malloc(total*sizeof(HTNode));
    if(!HT)
       return ERROR;
    for(i=0;i<n;i++){
        HT[i].Lchild=-1;
        HT[i].parent=-1;
        HT[i].Rchild=-1;
        HT[i].Weight=*wet;
        wet++;
    }

    /*对n到total的分量进行初始化*/
    for(i;i<total;i++){
        HT[i].Lchild=-1;
        HT[i].Rchild=-1;
        HT[i].parent=-1;
        HT[i].Weight=0;
    }
    /*用HT的后n-1个分量存储霍夫曼树*/
    /*调用函数SelectMin找出森林中两棵权重最小的树*/
    int min1=0,min2=0;
    for(i=n;i<total;i++){
        SelectMin(HT,i,min1,min2);
        HT[min1].parent=i;
        HT[min2].parent=i;
        HT[i].Lchild=min1;
        HT[i].Rchild=min2;
        HT[i].Weight=HT[min1].Weight+HT[min2].Weight;
    }
    return HT;
} 

/*从叶子节点开始逆向进行霍夫曼编码*/
/*HC用来储存霍夫曼编码值*/
Status HuffmanCoding(HuffmanTree HT,HuffmanCode &HC,int n){
    /*HC本身是一个char类型数组的指针,其指向n个char类型的地址,所以给HC分配内存应该写成下面那样*/
    HC=(HuffmanCode)malloc(n*sizeof(char *));
    if(!HC)
       return ERROR;
    /*声明一个动态数组code,用来临时存储霍夫曼编码,数组含有n-1个霍夫曼码,加上一个‘\0‘终止符正好是n个元素,所以分配内存时*/
    char *code;
    code=(char *)malloc(n*sizeof(char));
    if(!code)
       return ERROR;
    code[n-1]=‘\0‘;/*让最后一个元素为终止符*/ 

    int i=0;
    for(i=0;i<n;i++){
        int current=i;
        int father=HT[i].parent;
        int start=n-1;
        while(father!=-1){
            if(current==HT[father].Lchild)
                code[--start]=‘0‘;
            else
                code[--start]=‘1‘;
            current=father;
            father=HT[father].parent;
        }
        /*HC[i]用于最终存储霍夫曼码,是char类型的数组,有n个char类型的数据*/
        HC[i]=(char *)malloc((n-start)*sizeof(char));
        if(!HC[i])
            return ERROR;
        /*从临时空间中复制到HC[i]中*/
        strcpy(HC[i],code+start);
    }
    /*释放临时存储空间*/
    free(code);
    code=NULL;
    return OK;
}

int main(void){
    int amount=0,i=0;
    int *wet=(int *)malloc(amount*sizeof(int));
    printf("请输入要编码的字符个数(个数为整型且>1)");
    scanf("%d",&amount);
    while(amount<=1){
        printf("字符个数必须大于1\n");
        scanf("%d",&amount);
    }
    printf("请输入要编码的字符的权值");
    for(i=0;i<amount;i++){
        scanf("%d",wet+i);
    }
    HuffmanTree HT;
    HT=CreateHuffmanTree(HT,wet,amount);

    HuffmanCode HC;
    HuffmanCoding(HT,HC,amount);
    for(i=0;i<amount;i++){
        puts(HC[i]);
    }
    free(wet);
    return OK;
}

参考了http://blog.csdn.net/ns_code/article/details/19174553这篇博客

2016-05-09更新:

今日在复习霍夫曼树的过程中,发现自己忽视了一些细节问题,造成了一些错误,下面加以记录并反思

问题一:出现在函数Min中

int Min(HuffmanTree HT,int k){
    int min=0,min_weight=0,i=0;
    /*注意此处是先寻找双亲不存在的节点再赋值,给min_weight赋值应该在循环以外*/
    while(HT[i].parent!=-1)
        i++;
    min_weight=HT[i].weight;
    min=i;
    for(i;i<k;i++){
        if(HT[i].weight<min_weight&&HT[i].parent==-1){
            min_weight=HT[i].weight;
            min=i;
        }
    }
    /*赋值之后切记要将其双亲赋值为1,否则会出现错误*/
    HT[min].parent=1;
    return min;
}

问题二:出现在CreateHuffmanTree函数中

HuffmanTree CreateHuffmanTree(HuffmanTree HT,int * wet,int amount){
    int i=0,min_weight=0,min=0;
    int total=2*amount-1;
    /*注意此处分配内存时,sizeof()中应该是HTNode!!要对HT的本质进行理解,HT是一棵树!*/
    HT=(HuffmanTree)malloc(total*sizeof(HTNode));
    if(!HT)
       return ERROR;
    for(i=0;i<amount;i++){
        HT[i].Lchild=-1;
        HT[i].parent=-1;
        HT[i].Rchild=-1;
        HT[i].weight=*wet;
        wet++;
    }
    for(i;i<total;i++){
        HT[i].Lchild=-1;
        HT[i].Rchild=-1;
        HT[i].parent=-1;
        HT[i].weight=0;
    }

    int min1=0,min2=0;
    /*注意此处i的初始值,要对这个过程加以理解,霍夫曼树是用amount之后的分量来存储的,故不能写i=0*/
    for(i=amount;i<total;i++){
        SelectMin(HT,min1,min2,i);
        HT[min1].parent=i;
        HT[min2].parent=i;
        HT[i].Lchild=min1;
        HT[i].Rchild=min2;
        HT[i].weight=HT[min1].weight+HT[min2].weight;/*注意新生成的节点的权值为选出的两个最小的节点的权值之和*/
    }
    return HT;
}

这个问题主要是对建立霍夫曼树的过程理解不够透彻,导致在为HT分配内存时写错大小,和循环过程中将i的初始值误写为0,今后应当注意。

问题三:出现在HuffmanCoding函数中

Status HuffmanCoding(HuffmanTree HT,HuffmanCode &HC,int amount){
    int i=0;
    /*要先给HC分配内存。HC存储了amount个指向霍夫曼码的指针*/
    HC=(HuffmanCode)malloc(amount*sizeof(char *));
    if(!HC)
      return ERROR;
    char * code;
    code=(char *)malloc(amount*sizeof(char));
    if(!code)
       return ERROR;
    code[amount-1]=‘\0‘;
    for(i=0;i<amount;i++){
        int current=i;
        int start=amount-1;
        int father=HT[i].parent;
        while(father!=-1){
            if(current==HT[father].Lchild)
                code[--start]=‘0‘;
            else
                code[--start]=‘1‘;
            current=father;
            father=HT[father].parent;
        }
        /*HC[i]是HC中若干个存储单元之一,存储着一个具体字符的霍夫曼码,所以分配内存空间时sizeof()中为char*/
        HC[i]=(char *)malloc((amount-start)*sizeof(char));
        if(!HC[i])
           return ERROR;
        strcpy(HC[i],code+start);
    }
    free(code);
    code=NULL;
}

对HC的用途理解不够到位,再次强调,HC是一个存储了若干个指向霍夫曼码的指针的数组,HC[i]则用于存储具体字符的霍夫曼码

问题四:反复出现在主函数之中,必须加以记录,认真反省

int main(void){
    HuffmanTree HT;
    int amount=0;
    printf("请输入需要编码的字符个数");
    scanf("%d",&amount);
    int i=0;
    int * wet=(int *)malloc(amount*sizeof(int));
    printf("请输入每个字符的权重");
    for(i=0;i<amount;i++){
        scanf("%d",wet+i);
    }
    /*此处忘记写HT=,导致未能成功建立霍夫曼树,由于这里要改变函数形参的值,一般情况考虑传入指针变量,但这个函数如果写成指针又太复杂,
    很容易出错,故这里使用return把建立好的霍夫曼树直接返回,会方便许多,但是切记要把返回值赋给HT!!!*/
    HT=CreateHuffmanTree(HT,wet,amount);
    HuffmanCode HC;
    HuffmanCoding(HT,HC,amount);
    for(i=0;i<amount;i++){
        puts(HC[i]);
    }
    free(wet);
} 
时间: 2024-10-14 09:50:40

霍夫曼树及霍夫曼编码的C语言实现的相关文章

Huffman tree(赫夫曼树、霍夫曼树、哈夫曼树、最优二叉树)

flyfish 2015-8-1 Huffman tree因为翻译不同所以有其他的名字 赫夫曼树.霍夫曼树.哈夫曼树 定义引用自严蔚敏<数据结构> 路径 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径. 路径长度 路径上的分支数目称作路径长度. 树的路径长度 树的路径长度就是从根节点到每一结点的路径长度之和. 结点的带权路径长度 结点的带权路径长度就是从该结点到根节点之间的路径长度与结点上权的乘积. 树的带权路径长度 树的带权路径长度就是树中所有叶子结点的带权路径长度之和,通常记做

哈夫曼树与哈夫曼编码

哈夫曼树与哈夫曼编码 术语: i)路径和路径长度 在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径. 路径中分支的数目称为路径长度.若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1. ii)结点的权及带权路径长度 若对树中的每个结点赋给一个有着某种含义的数值,则这个数值称为该结点的权. 结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积. iii)树的带权路径长度 树的带权路径长度:所有叶子结点的带权路径长度之和,记为WPL. 先了解一下

【数据结构】树与树的表示、二叉树存储结构及其遍历、二叉搜索树、平衡二叉树、堆、哈夫曼树与哈夫曼编码、集合及其运算

1.树与树的表示 什么是树? 客观世界中许多事物存在层次关系 人类社会家谱 社会组织结构 图书信息管理 分层次组织在管理上具有更高的效率! 数据管理的基本操作之一:查找(根据某个给定关键字K,从集合R 中找出关键字与K 相同的记录).一个自然的问题就是,如何实现有效率的查找? 静态查找:集合中记录是固定的,没有插入和删除操作,只有查找 动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除 静态查找--方法一:顺序查找(时间复杂度O(n)) int SequentialSearch(St

【算法总结】哈夫曼树和哈夫曼编码

一.哈夫曼树 1. 哈夫曼树也称最优二叉树. 叶子节点的权值是对叶子节点赋予的一个有意义的数值量. 设二叉树具有 n 个带权值的叶子结点,从根节点到各个叶子结点的路径长度与相应叶子结点权值的乘积之和叫做二叉树的带权路径长度. 给定一组具有确定权值的叶子结点,可以构造处不同的二叉树,将其中带权路径长度最小的二叉树称为哈夫曼树. 2. 基本思想: 初始化:由给定的 n 个权值 $\left\{ \omega_{1},\omega_{2},\cdots ,\omega_{n}\right\}$构造 n

[数据结构] AVL树和AVL旋转、哈夫曼树和哈夫曼编码

1. AVL树 AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为高度平衡树.查找.插入和删除在平均和最坏情况下都是O(log n).增加和删除可能需要通过一次或多次树旋转来重新平衡这个树. 节点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反).带有平衡因子1.0或 -1的节点被认为是平衡的.带有平衡因子 -2或2的节点被认为是不平衡的,并需要重新平衡这个树.平衡因子可以直接存储在每个节点中,或从可能存储在节点中的子树高度计算出来. 1.2AVL旋转 AVL树的基本操作一

《数据结构复习笔记》--哈夫曼树,哈夫曼编码

先来了解一下哈夫曼树. 带权路径长度(WPL):设二叉树有n个叶子结点,每个叶子结点带有权值 wk,从根结点到每个叶子结点的长度为 lk,则每个叶子结点的带权路径长度之和就是: 最优二叉树或哈夫曼树: WPL最小的二叉树. [例]有五个叶子结点,它们的权值为{1,2,3,4,5},用此权值序列可以构造出形状不同的多个二叉树. 其中结果wpl最小值的是:33=(1+2)*3+(3)*2+(4+5)*2: 哈夫曼树的构造: 每次把权值最小的两棵二叉树合并, 代码: typedef struct Tr

哈夫曼树以及哈夫曼编码的问题

今天看到一个哈夫曼编码的题目,给定一个字符串abcdabaa,问哈夫曼编码后的二进制串的总长度是多少,答案是14 对于哈夫曼树我是一点都不了解啊,所以一顿查找,总结出以下知识点,与大家分享:当然部分内容参考了下百度 哈夫曼树又称为最优二叉树,是一种带权路径最短的二叉树.哈夫曼树是二叉树的一种应用,在信息检索中很常用. 一些相关的概念: 1.节点之间的路径长度:从一个节点到另一个节点之间的分支数量称为两个节点之间的路径长度. 2.树的路径长度:从根节点到树中每一个节点的路径长度之和. 3.节点的带

哈夫曼树及哈夫曼编码

一,引言 如上图,是一个判断体重在什么范围内的判定树,例如,学校体检的时候,我们反复用这个算法,当你输入一个体重:200斤,然后程序就开始反复判断了,经过三次判断,它发现你过重,然后重启系统了,又来一个人,还是200斤,三次判断之后,又系统重启了-后面的200多个200多斤的盘子判断完了之后,来了个120的,终于是个比较正常的体重了,但是系统一判断完,系统还是重启,反复检查之后,发现你那台8086时代的电脑终于撑不住了~ 于是你改了下算法,换了一棵判定树,这次,先判断这个人是不是个200多斤的胖

哈夫曼树和哈夫曼编码

在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN)树和哈夫曼编码.哈夫曼编码是哈夫曼树的一个应用.哈夫曼编码应用广泛,如JPEG中就应用了哈夫曼编码. 首先介绍什么是哈夫曼树. 哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树.所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的 路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数).树的带权路径长度记为WPL= (W1*L1+W2*L2+W3*L3+...+Wn*Ln),N个权值W