博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
哈夫曼编码的理解以及简单实现
阅读量:3948 次
发布时间:2019-05-24

本文共 3829 字,大约阅读时间需要 12 分钟。

哈夫曼树

在介绍哈夫曼编码前,我们先来了解一下哈夫曼树。

美国科学家哈夫曼在1952年发现了哈夫曼编码,为了纪念他的成就,于是把他在编码中用到的特殊二叉树称之为哈夫曼树,这种编码方法称之为哈夫曼编码。
那在介绍哈夫曼树之前在补充一些基础知识
在这里插入图片描述
结点的路径长度:从根结点到该结点的路径上分支的数目。

树的路径长度:树中每个结点的路径长度之和。

结点的带权路径长度:该结点到根结点之间的路径长度与结点上权的乘积。

树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和。

上图的树路径长度就是:1+2+3+3+2+1+2+2 = 16,WPL:5x3+15x3+40x2+30x2+10x2 = 220。

哈夫曼树就是WPL最小的二叉树,那上图所示的二叉树是不是最优的二叉树呢?就让我们一起来来验证一下吧。

1. 先把有权值的叶子结点从小到大排序

(A5,E10,B15,D30,C40)
2. 每次取最小的两个结点合并为一个新的结点
3. 删除最小的两个结点,将新的结点插入到序列中并保证序列有序
4. 重复2,3步,直至所有叶子结点都已被合并,找到根结点

在这里插入图片描述WPL = 40x1+30x2+15x3+10x4+5x4 = 205,比之前的220小了15,显然此时的这颗二叉树才是哈夫曼树。

哈夫曼编码

在理解了哈夫曼树之后,我们来看一下哈夫曼编码。

哈夫曼编码发明的时侯的目的是为了解决当年远距离通信的数据传输的最优化问题。该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字。

在网络上我们要传输一段内容给对方(显然要通过二进制的方式),假设这段内容中只有A,B,C,D,E,F六个字母,那么可以表示为:

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

如果我们要传输的是 BADCADFEED,那么转化过后为 001000011010000011101100100011。那么问题来了,如果要传输的内容很大,那转化后的二进制串将是非常长…,这种方式效率实在是有些可怕。

假设六个字母出现的频率(权值)为A 27,B 8,C 15,D 15,E 30,F 5,我们按照和夫曼树的规则来规划它们。

构建哈夫曼树。

在这里插入图片描述

将左分支都改为0,右分支改为1(也可右0左1)。
在这里插入图片描述
这样用0,1表示后,每个字符的编码就变成了下表:

字母 A B C D E F
二进制 01 1001 101 00 11 1000

二进制串:1001010010101001000111100

可以看到,出现频率越高的字符转化后的二进制编码就越短,而这也是哈夫曼编码节省资源的要点所在(权重越大,编码越短)。

这节约了大概17%的资源,而随着字符的增多和多字符权重的不同,这种压缩的优势会更加明显。

前缀编码

编码中非0即1,长短不等的话是很容易混淆的,所以若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这在编码成为前缀编码而哈夫曼编码便是最优的前缀码。

注:既然我们已经编码成功发送给对方,那对方如何解码呢?当然还是通过哈夫曼树,所以双方应该提前约定好同样的哈夫曼编码规则。

简单实现

#include
#include
#include
using namespace std;#define Lnum 99/* 哈夫曼编码 */typedef struct{
double weight; //权值 int parent,lchild,rchild; //父结点,孩子结点}HuffmanTree;typedef struct{
int node_num; double weight;}Node;typedef struct{
char ch; //储存字符 char bits[Lnum]; //编码位串}HuffmanCode;int n,m; //全局变量(n—叶子结点树,m—总结点数)bool cmp(Node& a, Node& b){
return a.weight < b.weight;}//寻找权值最小和次小的结点void SearchMin(HuffmanTree *T, int flag, int *p1, int *p2){
Node N[m]; int j = 0; for(int i = 0; i < flag; ++i) {
if(T[i].parent == -1) //如果还没有被合并 {
N[j].node_num = i; N[j].weight = T[i].weight; ++j; } } sort(N, N + j, cmp); //按权值从小到大排序 //前面两个是所需要的结点 *p1 = N[0].node_num; *p2 = N[1].node_num;}//创建哈夫曼树void CreatHuffmanTree(HuffmanTree *T){
int p1,p2; //初始化哈夫曼树 //将2n-1个结点里的三个指针均置为空(即置为-1),权值置为0 for(int i = 0; i < m; ++i) {
T[i].weight = 0; T[i].parent = -1; T[i].lchild = -1; T[i].rchild = -1; } cout << "请输入" << n << "个叶子结点的权值:"; for(int i = 0; i < n; i++) cin >> T[i].weight; for(int i = n; i < m; i++) {
//合并结点(每次取权值最小和次小的结点),合并m - n(==n - 1)次 SearchMin(T, i, &p1, &p2); T[p1].parent = T[p2].parent = i; T[i].lchild = p1; //权值最小的结点为左孩子 T[i].rchild = p2; T[i].weight = T[p1].weight + T[p2].weight; }}//哈夫曼编码void HuffmanCoding(HuffmanTree *T, HuffmanCode *C){
int p_child,p_parent; //指向孩子及父结点 char temp[n]; //临时存放编码 int begin; //标记编码在temp中开始的位置(因为是从下往上回溯) temp[n - 1] = '\0'; cout << "请输入" << n << "个叶子结点所代表的字符:"; for(int i = 0; i < n; i++) {
cin >> C[i].ch; begin = n - 1; //开始从下往上回溯 p_child = i; while((p_parent = T[p_child].parent) != -1) {
if(T[p_parent].lchild == p_child) //左孩子 temp[--begin] = '0'; else temp[--begin] = '1'; p_child = p_parent; //继续往上回溯 } strcpy(C[i].bits, &temp[begin]); //复制格式正确的编码 }}int main(){
cout << "确定有几个叶子结点:"; cin >> n; m = 2 * n - 1; //总结点数 HuffmanTree T[m]; HuffmanCode C[n]; CreatHuffmanTree(T); HuffmanCoding(T, C); cout << "哈夫曼编码:" << endl; for(int i = 0; i < n; i++) printf("字符: %c, 编码: %s\n", C[i].ch, C[i].bits); return 0;}

在这里插入图片描述

转载地址:http://ajqwi.baihongyu.com/

你可能感兴趣的文章
营销中的“战略非对称”
查看>>
android 如何开关Mediatek开发的Feature
查看>>
Android电话功能各部分深入探讨
查看>>
Android应用技巧总结
查看>>
Android创建sdcard详细图解
查看>>
Android开发:如何实现TCP和UDP传输
查看>>
Android电源管理相关应用技巧分享
查看>>
Android录音失真具体解决方案
查看>>
Android根文件系统相关应用介绍
查看>>
Android文件系统深入剖析
查看>>
Android判断网络状态方法详解
查看>>
在Android上实现Junit单元测试的四部曲
查看>>
有效控制Android应用程序的耗电量
查看>>
Android术语列表概览
查看>>
全方位解读Android多媒体框架源码
查看>>
Android音乐编程的管理音频硬件
查看>>
Android UI控件组合应用之一:建立数据模型
查看>>
避免Andriod平台图片失真的图片形式
查看>>
Android之Gridview图片列表
查看>>
objdump的使用方法
查看>>