哈夫曼编码(最优前缀码)

2023-10-28

        作为哈夫曼树的一个重要应用,我们来介绍哈夫曼编码。在我的上一篇博文《树之哈夫曼树》中已经介绍了建立哈夫曼树的过程,而由哈夫曼树求得的编码为最优前缀码。每个叶子表示的字符的编码,就是从根到叶子的路径上的标号依次相连所形成的编码,显然这就是该字符的最优前缀码。所谓前缀码是指,对字符集进行编码时,要求字符集中任一字符的编码都不是其它字符的编码的前缀,比如常见的等长编码就是前缀码。所谓最优前缀码是指,平均码长或文件总长最小的前缀编码称为最优的前缀码(这里的平均码长相当于码长的期望值)。

        我们知道,变长编码可能使解码产生二义性,而前缀码的出现很好地解决了这个问题。而平均码长相当于二叉树的加权路径长度,从这个意义上说,由哈夫曼树生成的编码一定是最优前缀码,故通常不加区分的将哈夫曼编码也称作最优前缀码。

        需要注意的是,由于哈夫曼树建立过程的不唯一性可知,生成的哈夫曼编码也是不唯一的,并且在本文中,将树中左分支和右分支分别标记为0和1也造成了哈夫曼编码的不唯一性(当然也可以反过来,将左分支记为1,右分支记为0)。

        在实际应用中,我们通常采用下列做法:根据各个字符的权值建立一颗哈夫曼树,求得每个字符的哈夫曼编码,有了每个字符的哈夫曼编码,我们就可以制作一个该字符集的哈夫曼编码表。有了字符集的哈夫曼编码表之后,对数据文件的编码过程是:依次读人文件中的字符c,在哈夫曼编码表H中找到此字符,将字符c转换为对应的哈夫曼编码串。对压缩后的数据文件进行解码则必须借助于哈夫曼树,其过程是:依次读人文件的二进制码,从哈夫曼树的根结点出发,若当前读入0,则走向左孩子,否则走向右孩子。一旦到达某一叶子时便译出相应的字符。然后重新从根出发继续译码,直至文件结束。下面给出制作哈夫曼编码表的过程的代码,通过以上的分析,读者不难写出文件编码过程和解码过程的代码。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

#define n 6           //叶子数目
#define m 2*n-1       //树中结点总数
typedef struct{       //结点类型
    double weight;    //结点的权值
    int parent,lchild,rchild;//双亲指针及左右孩子
}HTNode;
typedef HTNode HuffmanTree[m];//HuffmanTree是向量类型

typedef struct{       //用于SelectMin函数中排序的结点类型
    int id;           //保存根结点在向量中的序号
    double weight;    //保存根结点的权值
}temp;

typedef struct{       //编码结点
    char ch;          //存储字符
    char bits[n+1];   //存放编码位串
}CodeNode;
typedef CodeNode HuffmanCode[n];

void InitHuffmanTree(HuffmanTree T){
    //初始化哈夫曼树
    //将2n-1个结点里的三个指针均置为空(即置为-1),权值置为0
    for(int i=0;i<m;i++){
        T[i].lchild=-1;
        T[i].rchild=-1;
        T[i].parent=-1;
        T[i].weight=0;
    }
}

void InputWeight(HuffmanTree T){
    //输入叶子权值
    //读人n个叶子的权值存于向量的前n个分量中
    for(int i=0;i<n;i++){
        double x;
        scanf("%lf",&x);
        T[i].weight=x;
    }
}

bool cmp(temp a,temp b){
    //用于排序的比较函数
    return a.weight<b.weight;
}

void SelectMin(HuffmanTree T,int k,int *p1,int *p2){
    //在前k个结点中选择权值最小和次小的根结点,其序号分别为p1和p2
    temp x[m];              //x向量为temp类型的向量
    int i,j;
    for(i=0,j=0;i<=k;i++){  //寻找最小和次小根节点的过程
        if(T[i].parent==-1){//如果是根节点,则进行如下操作
            x[j].id=i;      //将该根节点的序号赋值给x
            x[j].weight=T[i].weight;//将该根节点的权值赋值给x
            j++;            //x向量的指针后移一位
        }
    }
    sort(x,x+j,cmp);        //对x按照权值从小到大排序
    //排序后的x向量的第一和第二个位置中存储的id是所找的根节点的序号值
    *p1=x[0].id;
    *p2=x[1].id;
}

void CreateHuffmanTree(HuffmanTree T){
    //构造哈夫曼树,T[m-1]为其根结点
    int i,p1,p2;
    InitHuffmanTree(T);    //将T初始化
    InputWeight(T);        //输入叶子权值
    for(i=n;i<m;i++){
        //在当前森林T[0..i-1]的所有结点中,选取权最小和次小的
        //两个根结点T[p1]和T[p2]作为合并对象
        //共进行n-1次合并,新结点依次存于T[i]中

        SelectMin(T,i-1,&p1,&p2);//选择权值最小和次小的根结点,其序号分别为p1和p2

        //将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树
        //新树的根是新结点T[i]
        T[p1].parent=T[p2].parent=i;//T[p1]和T[p2]的两棵树的根结点指向i
        T[i].lchild=p1;             //最小权的根结点是新结点的左孩子
        T[i].rchild=p2;             //次小权的根结点是新结点的右孩子
        T[i].weight=T[p1].weight+T[p2].weight;//新结点的权值是左右子树的权值之和
    }
}

void CharSetHuffmanEncoding(HuffmanTree T,HuffmanCode H){
    //根据哈夫曼树T求哈夫曼编码表H
    int c,p;//c和p分别指示T中孩子和双亲的位置
    char cd[n+1];//临时存放编码
    int start;//指示编码在cd中的起始位置
    cd[n]='\0';//编码结束符
    getchar();
    for(int i=0;i<n;i++){//依次求叶子T[i]的编码
        H[i].ch=getchar();//读入叶子T[i]对应的字符
        start=n;//编码起始位置的初值
        c=i;//从叶子T[i]开始上溯
        while((p=T[c].parent)>=0){//直至上溯到T[c]是树根为止
            //若T[c]是T[p]的左孩子,则生成代码0;否则生成代码1
            if(T[p].lchild==c)
                cd[--start]='0';
            else
                cd[--start]='1';
            c=p;//继续上溯
        }
        strcpy(H[i].bits,&cd[start]);//复制编码位串
    }
}

//*************************测试函数**********************************
int main(){
    HuffmanTree T;
    HuffmanCode H;
    printf("请输入%d个叶子结点的权值来建立哈夫曼树:\n",n);
    CreateHuffmanTree(T);
    printf("请输入%d个叶子结点所代表的字符:\n",n);
    CharSetHuffmanEncoding(T,H);
    printf("哈夫曼树已经建好,哈夫曼编码已经完成,输出如下:\n");
    printf("哈夫曼树:\n");
    for(int i=0;i<m;i++){
        printf("id:%d  weight:%.1lf   parent:%d",i,T[i].weight,T[i].parent);
        printf("  lchild:%d rchild:%d\n",T[i].lchild,T[i].rchild);
    }
    printf("哈夫曼编码:\n");
    double wpl=0.0;
    for(int i=0;i<n;i++){
        printf("id:%d   ch:%c  code:%s\n",i,H[i].ch,H[i].bits);
        wpl+=strlen(H[i].bits)*T[i].weight;
    }
    printf("平均码长为:%.2lf\n",wpl);
    return 0;
}

测试样例及预测运行结果:(表中和图中的权重数值weight均需要乘以0.01)



运行结果:



本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

哈夫曼编码(最优前缀码) 的相关文章

随机推荐

  • 分支与循环语句_C语言入门

    目录 2 分支语句 什么是分支语句 2 1 if 语句 2 1 1 单分支 2 1 2 双分支 2 1 3 多分支 2 2 switch 选择结构 2 2 2 default 子句 3 循环语句 3 1for 循环 3 1 1 for循环的
  • Tomcat 详解(安装Tomcat、启动和配置,发布一个web网站)

    接下来开始 Tomcat DE 详细介绍 文章目录 一 安装Tomcat 二 Tomcat 启动和配置 2 1 目录文件 2 2 启动 Tomcat 测试 2 3 关闭 Tomcat 三 配置 四 发布一个 web 网站 五 常见面试题 一
  • Android系统之Bundle用法

    1 Bundle概述 Bundle在Android开发中非常常见 它的作用主要时用于传递数据 Bundle传递的数据包括 string int boolean byte float long double等基本类型或它们对应的数组 也可以是
  • springboot项目maven老是加载不了jar包如SpringBootApplication找不到

    困扰我好久了所以开贴记录一下 解决办法 我是清理了一些里面的杂乱的项目 就好了 具体的原因不太清楚 不过 应该事其他的项目造成的干扰 导致项目不能加载 以上方法不知道所以 下面方法可以尝试 在Maven命令后加入参数 Dmaven wago
  • 华为OD机试真题-静态代码扫描服务【2023Q1】【JAVA、Python、C++】

    题目描述 静态扫描快速快速识别源代码的缺陷 静态扫描的结果以扫描报告作为输出 1 文件扫描的成本和文件大小相关 如果文件大小为N 则扫描成本为N个金币 2 扫描报告的缓存成本和文件大小无关 每缓存一个报告需要M个金币 3 扫描报告缓存后 后
  • StringUtils详细介绍

    转自 https www oschina net code snippet 239959 8724 commentform public static void TestStr null 和 操作 判断是否Null 或者 System ou
  • 【Shell牛客刷题系列】SHELL16 判断输入的是否为IP地址:来练习正则表达式~

    该系列是基于牛客Shell题库 针对具体题目进行查漏补缺 学习相应的命令 刷题链接 牛客题霸 Shell篇 该系列文章都放到专栏下 专栏链接为 专栏 Linux 欢迎关注专栏 本文知识预告 本文首先结合之前正则表达式的相关知识 给出了IP地
  • 苹果ipcc下载

    http ax phobos apple com edgesuite net WebObjects MZStore woa wa com apple jingle appserver client MZITunesClientCheck v
  • 飞桨学习笔记之经典图像分类模型

    1 LeNet 2 AlexNet 3 VGG 4 GoogLeNet 5 ResNet 1 LeNet LeNet 通过连续使用卷积和池化层的组合提取图像特征 网络结构示意图 第一模块 包含5 5的6通道卷积和2 2的池化 卷积提取图像中
  • request_time和upstream_response_time详解

    下图是request time 下图是upstream response time 精准的描述就是 request time是从接收到客户端的第一个字节开始 到把所有的响应数据都发送完为止 upstream response time是从与
  • CTK系列之编译

    CTK编译 CTK编译笔记 资源下载 CTK 源码下载 master ctk github 链接 cmake环境准备 下载最新版本cmake的window安装包直接安装即可 camke下载链接 编译前准备 以本次安装为例 安装目录选择实在C
  • 考研复试数据库原理课后习题(十五)——数据仓库和联机分析处理技术

    数据仓库和联机分析处理技术 1 数据仓库的4个基本特征是什么 数据仓库的4个基本特征如下 数据仓库的数据是面向主题的 主题是一个抽象的概念 是在较高层次上将企业信息系统中的数据综合 归类并进行分析利用的抽象 面向主题的数据组织方式是根据分析
  • Python执行windows命令,报中文乱码解决方法

    笔者通过subprocess模块在windows机器上执行ping 服务器命令 结果报如下错误 Ping hostname 在这里可以用python的第三发插件chardet来探测一下原有字符是什么编码 如果没有安装 可以用命令 pip i
  • 闻达(wenda+chatGLM-6B)一键部署包

    本文是 基于闻达 wenda chatGLM 6B 构建自己的知识库小助手 的一键部署包 将处理数据 模型启动整理为 可执行脚本 并提供内置python虚拟环境 无需任何额外的环境配置 只需要上传自己的知识库文件即可使用 部署包链接 链接
  • 硬件系统工程师宝典(14)-----建议收藏!常用的多层板叠层结构“大揭秘”

    各位同学大家好 欢迎继续做客电子工程学习圈 今天我们继续来讲这本书 硬件系统工程师宝典 上篇我们说到PCB的布局可根据功能 频率 信号类型划分 布局时考虑敏感信号远离噪声源 今天我们来看看多层板的常用叠层结构 四层板的叠层结构 四层板的叠层
  • 用python对excel进行批量处理(1):将表格中的url用requests替换成图片

    前言 事情是这样的 npy说今天的工作里有一个900个项目的excel 表格的样子大概如下图所示 需求呢是将下图中的url全部替换成对应的图片添加到excel中 作为一个程序员 这样的任务要是手动做真是太难为人了 所以打算帮npy减轻一下负
  • (尚硅谷)JavaWeb新版教程08-QQZone项目的实现

    目录 1 熟悉 QQZone 业务需求 2 数据库设计 2 1 抽取实体 2 2 分析其中的属性 2 3 分析实体之间的关系 2 4 数据库的范式 3 根据数据库的表新建 pojo 类 ORM编程思想 3 1 UserBasic 类 3 2
  • CTFshow web入门 web3

    CTFshow web入门 web3 题目提示没思路的时候进行抓一下包 这道题还可以用burpsuite来抓 但是感觉有点大材小用 直接用Network做比较简单点
  • Pyqt5+python2.7 美观的登陆界面制作(二)

    简介 继上一篇的安装 这一篇主要讲下登陆界面的制作 之后也会把自己做毕业设计中在csdn上没有找到自己解决的一些界面上的问题解决方法共享出来 最近事情比较多 更新会有点慢 但还是希望能对大家有帮助啦 先上一张我毕业设计最后做出来的登陆界面效
  • 哈夫曼编码(最优前缀码)

    作为哈夫曼树的一个重要应用 我们来介绍哈夫曼编码 在我的上一篇博文 树之哈夫曼树 中已经介绍了建立哈夫曼树的过程 而由哈夫曼树求得的编码为最优前缀码 每个叶子表示的字符的编码 就是从根到叶子的路径上的标号依次相连所形成的编码 显然这就是该字