Huffman树及其应用

哈夫曼树又称为最优二叉树,哈夫曼树的一个最主要的应用就是哈夫曼编码,本文通过简单的问题举例阐释哈夫曼编码的由来,并用哈夫曼树的方法构造哈夫曼编码,最终解决问题来更好的认识哈夫曼树的应用--哈夫曼编码。

一、引子

在学习中我们经常遇到将各科成绩改为优秀、良好、中等、及格和不及格。那么根据分级原理,代码表示为:

if(a<60)
   b = "不及格“;
else if(a<70)
  b = "及格";
else if(a<80)
  b = "中等";
else if(a<90)
  b = "良好";
else if(a<70)
  b = "优秀";
 

那么用二叉树表示为:

在实际应用中,5个学生等级的分布规律如表所示

分数 0~59 60~ 69 70~79 80~89 90~100
所占比例 5% 15% 40% 30% 10%

在上面中70分以上的比例是80%,但都需要经过三次比较判断才得出结果,显然不合理。

Huffman提出了想法,加入修改成如图所示

二、Huffman定义和原理

根据上面例子的引出,我们将各个成绩所占比例,当做权重标示在分支上,如图所示

哈夫曼树定义为:给定n个权值作为n个叶子结点,构造出的一棵二叉树带权路径长度达到最小。

1、路径和路径长度

在一棵树中,从一个结点往下可以达到的孩子或子孙结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。在二叉树a中根节点到结点D路径长度为4,在二叉树b中结点D到根节点路径长度为2.

树的路径长度:树根到每个结点的路径长度之和。二叉树a = 1+1+2+2+3+3+4+4= 20.二叉树B = 1+2+3+3+2+1+2+2 = 16.

2、结点的权及带权路径长度

若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

3、树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和。

树的带权路径长度计算

二叉树a = 5*1+15*2+40*3+30*4+10*4 = 315;

二叉树b = 5*3+15*3+40*2+30*2+10*2 = 220;

如果我们现在用10000个学生需要转换,二叉树a需要31500(别往里是百分数,315/100*10000),二叉树b需要22000次,差不多少了三分之一呢。

   从定义中可以看出哈夫曼树的一个最重要的特点:带权路径长度最短。

huffman树构建

哈夫曼编码步骤:

一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)

二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。

三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。

四、重复二和三两步,直到集合F中只有一棵二叉树为止。

简易的理解就是,假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图:

虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图:

再依次建立哈夫曼树,如下图:

其中各个权值替换对应的字符即为下图:

所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010

Huffman编码

赫夫曼在研究这种最优二叉树时的主要目的是解决当年远距离通信(主要是电报)的数据传输的最优化问题。比如传输一串字符“BADCADFEED”,采用二进制数据表示,如下表:

字母 A B C D E F
二进制字符 000 001 010 011 100 101

编码之后的二进制数据流为“001000011010000011101100100011”,对方接收时同样按照3位一组解码。现在假设这6个字母出现的频率不同,A 27%,B %8,C 15%,D 15%,E 30%,F 5%。下面将27、8、15、15、30、5分别作为A、B、C、D、E、F的权值构造赫夫曼树,如下图:

将右图赫夫曼树的权值左分支改为0,右分支改为1。

现在将这6个字母用从根节点到叶子所经过路径的0或1来编码,得到的编码表如下:

字母 A B C D E F
编码 01 1001 101 00 11 1000

将“BADCADFEED”再次编码得到“1001010010101001000111100”,共25个字符,与之前编码得到的30个字符相比大约节约了17%的存储和传输成本。

在解码时,用同样的赫夫曼树,即发送方和接收方约定好同样的赫夫曼编码规则。当接收方接收到“1001010010101001000111100”时,比对右图中的赫夫曼树。

代码

#include<iostream>
#include<string>
using namespace std;

//结点类型
struct element
{
    double weight;    //字符出现的概率为实数
    char ch;
    int lchild, rchild, parent;
};

//在HuffTer中找权值最小的两个结点i1和i2
void Select(element huffTree[], int *a, int *b, int n)
{
    int i;
    double weight = 0;
    for(i = 0; i <n; i++)
    {
        if(huffTree[i].parent != - 1)        //如果有父结点的,不进行判断
            continue;
        else
        {
            if(weight == 0)
            {
                weight = huffTree[i].weight;
                *a = i;
            }
            else
            {
                if(huffTree[i].weight < weight)
                {
                    weight = huffTree[i].weight;
                    *a = i;
                }
            }
        }
    }
    weight = 0;
    for(i = 0; i < n; i++)
    {
        if(huffTree[i].parent != -1 || (i == *a))
            continue;
        else
        {
            if(weight == 0)
            {
                weight = huffTree[i].weight;
                *b = i;
            }
            else
            {
                if(huffTree[i].weight  < weight)
                {
                    weight = huffTree[i].weight;
                    *b = i;
                }
            }
        }
    }
    int temp;
    if(huffTree[*a].lchild < huffTree[*b].lchild)        //避免根结点的左右子树混淆
    {
        temp = *a;
        *a = *b;
        *b = temp;
    }
}

//建立霍夫曼树
void HuffmanTree(element huffTree[], int w[], char ch[], int n)
{
    for(int i = 0; i < 2 * n - 1;i++) //霍夫曼树共有2*n - 1个结点
    {
        huffTree[i].parent = -1;    //双亲结点
        huffTree[i].lchild = -1;    //左孩子结点
        huffTree[i].rchild = -1;    //右孩子结点
    }
    for(int i = 0; i < n; i++)        //构造n棵只含有根结点的二叉树
    {
        huffTree[i].weight = w[i];    //给哈夫曼树赋权值
        huffTree[i].ch = ch[i];        //需要编码的字符
    }
    for(int k = n; k < 2 * n - 1; k++)//n-1次合并
    {
        int i1 = 0;
        int i2 = 0;
        Select(huffTree,&i1,&i2,k);    //在HuffTer中找权值最小的两个结点i1和i2
        huffTree[i1].parent = k;    //将i1和i2合并,则i1和i2的双亲是k
        huffTree[i2].parent = k;
        huffTree[k].weight = huffTree[i1].weight + huffTree[i2].weight;
        huffTree[k].lchild = i1;
        huffTree[k].rchild = i2;
    }
}

//霍夫曼编码
void HuffmanCode(element huffTree[], int n)
{
    int i, j,k;
    string s = "";
    for(i = 0; i < n; i++)    //在数组HuffTree中前n个元素是叶子结点,需要编码
    {
        s = "";            //编码s初始化为空串
        j = i;                    //暂存i,不破坏循环变量
        while(huffTree[j].parent != -1)    //结点j存在双亲
        {
            k = huffTree[j].parent;
            if(j == huffTree[k].lchild)    //结点j是其双亲的左孩子
            {
                s = s + "0";
            }
            else                //结点j是其双亲的右孩子
            {
                s = s + "1";
            }
            j = huffTree[j].parent;    //将结点j的双亲赋给j
        }
        cout<<"字符"<<huffTree[i].ch<<"的编码:"<<endl;
        for(int i =s.size() - 1; i >= 0; i--)    //将s作为结点i的编码逆序输出
        {
            cout<<s.at(i)<<" ";
        }
        cout<<endl;
    }
}

int main()
{
    const int n = 6;
    element huffTree[2 * n];
    char ch[] = {‘a‘, ‘b‘, ‘c‘,‘d‘,‘e‘,‘f‘};
    int w[] = {50, 60, 150, 200, 240, 300};
    // 构造霍夫曼树
    HuffmanTree(huffTree,w,ch,n);
    //编码
    HuffmanCode(huffTree,n);
    system("pause");
    return 0;
}
时间: 2024-08-26 18:41:13

Huffman树及其应用的相关文章

Huffman树与编码

带权路径最小的二叉树称为最优二叉树或Huffman(哈夫曼树). Huffman树的构造 将节点的权值存入数组中,由数组开始构造Huffman树.初始化指针数组,指针指向含有权值的孤立节点. b = malloc(n*sizeof(BTreeNode)); for (i = 0; i < n; i++) { b[i] = malloc(sizeof(BTreeNode)); b[i]->data = a[i]; b[i]->left = NULL; b[i]->right = NU

数据结构之huffman树

#include <stdio.h> #include<stdlib.h> #define MAXBIT      99 #define MAXVALUE  9999 #define MAXLEAF     30 #define MAXNODE    MAXLEAF*2 -1 typedef struct  {     int bit[MAXBIT];     int start; } HCodeType;        /* 编码结构体 */ typedef struct {  

Huffman树的编码译码

上个学期做的课程设计,关于Huffman树的编码译码. 要求: 输入Huffman树各个叶结点的字符和权值,建立Huffman树并执行编码操作 输入一行仅由01组成的电文字符串,根据建立的Huffman树进行译码操作,程序最后输出译码后的结果 Huffman.h定义了树的结点信息,各种操作.GCC编译通过. 1 #ifndef HUFFMAN_H_INCLUDED 2 #define HUFFMAN_H_INCLUDED 3 #include <iostream> 4 #include <

poj 3253 Fence Repair(模拟huffman树 + 优先队列)

题意:如果要切断一个长度为a的木条需要花费代价a, 问要切出要求的n个木条所需的最小代价. 思路:模拟huffman树,每次选取最小的两个数加入结果,再将这两个数的和加入队列. 注意priority_queue的用法,原型: 1 priority_queue<Type> q; 2 priority_queue<Type,deque<Type>,Comp> q; 其中Type是类型,Comp是比较结构体,比较函数是它的括号重载,比如对int型从小到大排序的Comp结构体如

数据结构之Huffman树与最优二叉树

最近在翻炒一些关于树的知识,发现一个比较有意思的二叉树,huffman树,对应到离散数学中的一种名为最优二叉树的路径结构,而Huffman的主要作用,最终可以归结到一种名为huffman编码的编码方式,使用huffman编码方式,我们可以以平均长度最短的码字来记录一串信息,且每个信息分子的编码唯一,独立.从而最终合成编码所对应的信息唯一,无歧义. huffman树的创建时基于每个信息分子都拥有其权重,权重越大,越靠近树根,即路径越短, 下面我们我们来以一个huffman树的例子为例:简单引入一下

NOI 2015 荷马史诗【BZOJ 4198】k叉Huffman树

抱歉因为NOIP集训,好长时间没再写题解了. NOI 2015也就只有这道题一看就能懂了…… 4198: [Noi2015]荷马史诗 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 922  Solved: 473[Submit][Status][Discuss] Description 追逐影子的人,自己就是影子. ——荷马 Allison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的<荷马史诗>.但是

【NOI2015】荷马史诗[Huffman树+贪心]

#130. [NOI2015]荷马史诗 统计 描述 提交 自定义测试 追逐影子的人,自己就是影子. ——荷马 Allison 最近迷上了文学.她喜欢在一个慵懒的午后,细细地品上一杯卡布奇诺,静静地阅读她爱不释手的<荷马史诗>.但是由<奥德赛>和<伊利亚特>组成的鸿篇巨制<荷马史诗>实在是太长了,Allison 想通过一种编码方式使得它变得短一些. 一部<荷马史诗>中有 nn 种不同的单词,从 11 到 nn 进行编号.其中第 ii 种单词出现的总

Huffman树的应用 (数据结构)

Huffman树的应用: 1.先选择一篇文章 2.然后统计字符个数 3.对个数不为0字符的进行编码 4.输出码文 5.进行译码 上机代码: /************************************************************************* > File Name: Huffman树的应用.cpp > Author: zzuspy > Mail: [email protected] > Created Time: 2014年12月3日

哈夫曼(Huffman)树与哈夫曼编码

声明:原创作品,转载时请注明文章来自SAP师太技术博客:www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将追究法律责任!原文链接:http://www.cnblogs.com/jiangzhengjun/p/4289610.html 哈夫曼树又称最优二叉树,是一种带权路径长最短的树.树的路径长度是从树根到每一个叶子之间的路径长度之和.节点的带树路径长度为从该节点到树根之间的路径长度与该节点权(比如字符在某串中的使用频率)的乘积. 比如有一串字符串如

Huffman树与最优二叉树续

OK,昨天我们对huffman数的基本知识,以及huffman树的创建做了一些简介,http://www.cnblogs.com/Frank-C/p/5017430.html 今天接着聊: huffman树创建完成之后,我们如何去得到huffman编码呢? 图12.4_1 huffman树形结构 图12.4_2  huffman存储结构(数组) 首先,以上面的树为例,我们必须明白几个要点: 1:从什么地方开始访问这颗树:根节点 , index 2n-1 = 15 2:访问的规则:向左为0  向右