Huffman编码解码

霍夫曼(Huffman)编码问题也就是最优编码问题,通过比较权值逐步构建一颗Huffman树,再由Huffman树进行编码、解码。

其步骤是先构建一个包含所有节点的线性表,每次选取最小权值的两个节点,生成一个父亲节点,该父亲节点的权值等于两节点权值之和,然后将该父亲节点加入到该线性表中,再重复上述步骤,直至构成一个二叉树,注意已经使用过的节点不参与。

Huffman编码贪心原理

编码原理

把每个字符看作一个单节点子树放在一个树集合中,每棵子树的权值等于相应字符的频率。每次取权值最小的两棵子树合成一棵新树,并重新放到集合中。新树的权值等于两棵子树权值之和。

贪心选择性

设xx和yy是频率最小的两个字符,则存在前缀码使得xx和yy具有相同码长,且仅有最后一位编码不同。换句话说,贪心选择保留了最优解。

优化子结构

设TT是加权字符集CC的最优编码树,xx和yy是树TT中两个叶子,且互为兄弟结点,zz是它们的父结点。若把zz看成具有频率f(z)=f(x)+f(y)f(z)=f(x)+f(y)的字符,则树T′=T−{x,y}T' = T - \{ x, y\}是字符集C′=C−{x,y}⋃{z}C' = C - \{ x, y\} \bigcup \{z\}的一棵最优编码树。换句话说,原问题的最优解包含子问题的最优解。

举例说明

编码表

字符 a b c d e f
频率 45 13 12 16 9 5
编码 0 101 100 111 1101 1100

Huffman编码

下面将解释为什么是这样编码,在解释之前先说明一个概念:

  • 前缀码:任何一个编码都不是另一个编码的前缀(prefix)。

如果Huffman编码符合前缀码的要求的话,那么绝不会出现编码二义性的问题。而且通过权值这一参考量,构成了最优编码。

原理图



Huffman编码解码算法实现

节点信息结构

// 节点信息结构
struct Node {// 值string value;// 权值float weight;// 父节点int parent;// 左子节点int lchild;// 右子节点int rchild;
};

编码信息结构

// 编码信息结构
struct Code {// 编码字符int bit[maxBit];// 开始位置int start;// 值string value;
};

全局常量和全局变量

const int INF = 1000000000;
const int maxBit = 1 << 5;
const int maxNode = 1 << 10;
const int maxCode = 1 << 10;// 节点数组
Node huffman[maxNode];
// 编码数组
Code huffmanCode[maxCode];
// n个字符串
int n;

初始化Huffman树

// 初始化Huffman树
void initHuffmanTree() {for(int i = 0; i < (2 * n) - 1; i++) {huffman[i].weight = 0;huffman[i].value = "";huffman[i].parent = -1;huffman[i].lchild = -1;huffman[i].rchild = -1;}
}

构造Huffman树

// 贪心法
// 构造Huffman树
void huffmanTree() {// 循环构建Huffman树for(int i = 0; i < n - 1; i++) {// m1,m2存放所有节点中权值最小的两个节点权值int m1 = INF;int m2 = INF;// x1,x2存放所有节点中权值最小的两个节点下标int x1 = 0;int x2 = 0;for(int j = 0; j < n + i; j++) {if(huffman[j].weight < m1 && huffman[j].parent == -1) {m2 = m1;x2 = x1;m1 = huffman[j].weight;x1 = j;} else if(huffman[j].weight < m2 && huffman[j].parent == -1) {m2 = huffman[j].weight;x2 = j;}}// 设置找到的两个节点的x1,x2的父节点信息huffman[x1].parent = n + i;huffman[x2].parent = n + i;huffman[n + i].weight = huffman[x1].weight + huffman[x2].weight;huffman[n + i].lchild = x1;huffman[n + i].rchild = x2;}
}

Huffman编码

// huffman编码
void huffmanEncoding() {// 临时结构Code cd;int child, parent;for(int i = 0; i < n; i++) {cd.value = huffman[i].value;cd.start = n - 1;child = i;parent = huffman[child].parent;// 未到根节点while(parent != -1) {// 左孩子if(huffman[parent].lchild == child) {cd.bit[cd.start] = 0;} else {// 右孩子cd.bit[cd.start] = 1;}cd.start--;// 设置下一循环条件child = parent;parent = huffman[child].parent;}// 保存求出的每个叶子节点的Huffman编码结构for(int j = cd.start + 1; j < n; j++) {huffmanCode[i].bit[j] = cd.bit[j];}huffmanCode[i].start = cd.start;huffmanCode[i].value = cd.value;}
}

打印Huffman编码信息

// 打印每个叶节点的Huffman编码和编码起始值
void printHuffmanCode() {for(int i = 0; i < n; i++) {cout << "第" << i + 1 << "个字符 " << huffmanCode[i].value << " 的Huffman编码为:";for(int j = huffmanCode[i].start + 1; j < n; j++) {cout << huffmanCode[i].bit[j];}cout << " 编码起始值为:" << huffmanCode[i].start << endl;}cout << endl;
}

解码Huffman编码

// 解码Huffman编码
void HuffmanDecoding(string s) {vector<string> v;// 标识位int ok = 1;for(int i = 0; i < s.length();) {// 根节点int x = (2 * n) - 1 - 1;// 不为叶子节点while(huffman[x].lchild != -1 && huffman[x].rchild != -1) {// 左子树if(s[i] == '0') {x = huffman[x].lchild;} else {// 右子树x = huffman[x].rchild;}i++;// 处理0,1序列有误// 这种情况一般是结尾0,1序列少了,导致最后一个字符串解码失败if(i == s.length() && huffman[x].lchild != -1) {ok = 0;break;}}if(ok) {v.push_back(huffman[x].value);}}if(ok) {for(int i = 0; i < v.size(); i++) {cout << v[i];}cout << endl << endl;} else {cout << "解码有误。" << endl << endl;}
}

主函数

int main() {while(true) {// 初始化// 输入数据cout << "请输入字符串个数(0退出):";cin >> n;if(!n) {break;}// 初始化Huffman树initHuffmanTree();for(int i = 0; i < n; i++) {cout << "一共" << n << "个字符串,请输入第" << i + 1 << "个字符串及其权值:";cin >> huffman[i].value;cin >> huffman[i].weight;}// 构造Huffman树huffmanTree();// huffman编码huffmanEncoding();// 打印每个叶节点的Huffman编码和编码起始值printHuffmanCode();while(true) {cout << "请输入一段符合上述编码的0,1序列(q进入下一次编码解码):";string s;cin >> s;if(s[0] == 'q') {cout << endl;break;}cout << "原始0,1序列为:" << s << endl;cout << "解码后为:";// 解码HuffmanDecoding(s);}}return 0;
}

测试主程序

#include <iostream>
#include <vector>
#include <string>using namespace std;const int INF = 1000000000;
const int maxBit = 1 << 5;
const int maxNode = 1 << 10;
const int maxCode = 1 << 10;// 节点信息结构
struct Node {// 值string value;// 权值float weight;// 父节点int parent;// 左子节点int lchild;// 右子节点int rchild;
};// 编码信息结构
struct Code {// 编码字符int bit[maxBit];// 开始位置int start;// 值string value;
};// 节点数组
Node huffman[maxNode];
// 编码数组
Code huffmanCode[maxCode];// n个字符串
int n;// 初始化Huffman树
void initHuffmanTree() {for(int i = 0; i < (2 * n) - 1; i++) {huffman[i].weight = 0;huffman[i].value = "";huffman[i].parent = -1;huffman[i].lchild = -1;huffman[i].rchild = -1;}
}// 贪心法
// 构造Huffman树
void huffmanTree() {// 循环构建Huffman树for(int i = 0; i < n - 1; i++) {// m1,m2存放所有节点中权值最小的两个节点权值int m1 = INF;int m2 = INF;// x1,x2存放所有节点中权值最小的两个节点下标int x1 = 0;int x2 = 0;for(int j = 0; j < n + i; j++) {if(huffman[j].weight < m1 && huffman[j].parent == -1) {m2 = m1;x2 = x1;m1 = huffman[j].weight;x1 = j;} else if(huffman[j].weight < m2 && huffman[j].parent == -1) {m2 = huffman[j].weight;x2 = j;}}// 设置找到的两个节点的x1,x2的父节点信息huffman[x1].parent = n + i;huffman[x2].parent = n + i;huffman[n + i].weight = huffman[x1].weight + huffman[x2].weight;huffman[n + i].lchild = x1;huffman[n + i].rchild = x2;}
}// huffman编码
void huffmanEncoding() {// 临时结构Code cd;int child, parent;for(int i = 0; i < n; i++) {cd.value = huffman[i].value;cd.start = n - 1;child = i;parent = huffman[child].parent;// 未到根节点while(parent != -1) {// 左孩子if(huffman[parent].lchild == child) {cd.bit[cd.start] = 0;} else {// 右孩子cd.bit[cd.start] = 1;}cd.start--;// 设置下一循环条件child = parent;parent = huffman[child].parent;}// 保存求出的每个叶子节点的Huffman编码结构for(int j = cd.start + 1; j < n; j++) {huffmanCode[i].bit[j] = cd.bit[j];}huffmanCode[i].start = cd.start;huffmanCode[i].value = cd.value;}
}// 打印每个叶节点的Huffman编码和编码起始值
void printHuffmanCode() {for(int i = 0; i < n; i++) {cout << "第" << i + 1 << "个字符 " << huffmanCode[i].value << " 的Huffman编码为:";for(int j = huffmanCode[i].start + 1; j < n; j++) {cout << huffmanCode[i].bit[j];}cout << " 编码起始值为:" << huffmanCode[i].start << endl;}cout << endl;
}// 解码Huffman编码
void HuffmanDecoding(string s) {vector<string> v;// 标识位int ok = 1;for(int i = 0; i < s.length();) {// 根节点int x = (2 * n) - 1 - 1;// 不为叶子节点while(huffman[x].lchild != -1 && huffman[x].rchild != -1) {// 左子树if(s[i] == '0') {x = huffman[x].lchild;} else {// 右子树x = huffman[x].rchild;}i++;// 处理0,1序列有误// 这种情况一般是结尾0,1序列少了,导致最后一个字符串解码失败if(i == s.length() && huffman[x].lchild != -1) {ok = 0;break;}}if(ok) {v.push_back(huffman[x].value);}}if(ok) {for(int i = 0; i < v.size(); i++) {cout << v[i];}cout << endl << endl;} else {cout << "解码有误。" << endl << endl;}
}int main() {while(true) {// 初始化// 输入数据cout << "请输入字符串个数(0退出):";cin >> n;if(!n) {break;}// 初始化Huffman树initHuffmanTree();for(int i = 0; i < n; i++) {cout << "一共" << n << "个字符串,请输入第" << i + 1 << "个字符串及其权值:";cin >> huffman[i].value;cin >> huffman[i].weight;}// 构造Huffman树huffmanTree();// huffman编码huffmanEncoding();// 打印每个叶节点的Huffman编码和编码起始值printHuffmanCode();while(true) {cout << "请输入一段符合上述编码的0,1序列(q进入下一次编码解码):";string s;cin >> s;if(s[0] == 'q') {cout << endl;break;}cout << "原始0,1序列为:" << s << endl;cout << "解码后为:";// 解码HuffmanDecoding(s);}}return 0;
}

输出数据

请输入字符串个数(0退出):6
一共6个字符串,请输入第1个字符串及其权值:a 45
一共6个字符串,请输入第2个字符串及其权值:b 13
一共6个字符串,请输入第3个字符串及其权值:c 12
一共6个字符串,请输入第4个字符串及其权值:d 16
一共6个字符串,请输入第5个字符串及其权值:e 9
一共6个字符串,请输入第6个字符串及其权值:f 5
第1个字符 a 的Huffman编码为:0 编码起始值为:4
第2个字符 b 的Huffman编码为:101 编码起始值为:2
第3个字符 c 的Huffman编码为:100 编码起始值为:2
第4个字符 d 的Huffman编码为:111 编码起始值为:2
第5个字符 e 的Huffman编码为:1101 编码起始值为:1
第6个字符 f 的Huffman编码为:1100 编码起始值为:1请输入一段符合上述编码的0,1序列(q进入下一次编码解码):010011110111011100
原始0,1序列为:010011110111011100
解码后为:acdbef请输入一段符合上述编码的0,1序列(q进入下一次编码解码):00010010110010111011101110011001111110101
原始0,1序列为:00010010110010111011101110011001111110101
解码后为:aaacbcbeeffddab请输入一段符合上述编码的0,1序列(q进入下一次编码解码):010110011
原始0,1序列为:010110011
解码后为:解码有误。请输入一段符合上述编码的0,1序列(q进入下一次编码解码):q请输入字符串个数(0退出):0Process returned 0 (0x0)   execution time : 16.174 s
Press any key to continue.

Huffman编码解码相关推荐

  1. DS二叉树——Huffman编码与解码(不含代码框架)

    题目描述 1.问题描述 给定n个字符及其对应的权值,构造Huffman树,并进行huffman编码和译(解)码. 构造Huffman树时,要求左子树根的权值小于.等于右子树根的权值. 进行Huffma ...

  2. python Huffman编码及解码

    Huffman编码及解码 # coding:utf-8#Tree-Node Type class Node:def __init__(self,freq):self.left = Noneself.r ...

  3. 利用Huffman树进行文本编码解码的实现

    --------------------------------- 功能:利用Huffman树进行文本编码解码的实现 环境:WinXP,VC6.0 输入:C:\\in.txt 输出:C:\\out.d ...

  4. Huffman编码与解码

    Huffman编码与解码 // @author: Folivora Li // @copyright Folivora Li/* 4.Huffman编码与解码 (必做)(Huffman编码.二叉树) ...

  5. 实验三Huffman编码与解码

    一.实验原理 Huffman编码实现的数据结构 Huffman编码为可变长编码,若各码字长度按照所对应符号出现概率的大小逆序排列,则其平均长度最小. 编码步骤: 1.将信源符号按照出现概率由大到小的顺 ...

  6. 技术图文:如何利用C#实现Huffman编码?

    背景 Huffman编码 在通信和数据压缩领域具有重要的应用. 在介绍 Huffman 编码具体实现之前,先介绍几个相关的概念. 概念1:树中结点的带权路径长度 – 根结点到该结点的路径长度与该结点权 ...

  7. huffman java_详解Huffman编码算法之Java实现

    Huffman编码介绍 Huffman编码处理的是字符以及字符对应的二进制的编码配对问题,分为编码和解码,目的是压缩字符对应的二进制数据长度.我们知道字符存贮和传输的时候都是二进制的(计算机只认识0/ ...

  8. Huffman 编码压缩算法

    为什么80%的码农都做不了架构师?>>>    前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过 David Huf ...

  9. huffman编码压缩算法

    转自:http://coolshell.cn/articles/7459.html 前两天发布那个rsync算法后,想看看数据压缩的算法,知道一个经典的压缩算法Huffman算法.相信大家应该听说过  ...

最新文章

  1. 鲁棒,抗遮挡的对柔性手抓取的物体6D姿态估计
  2. 分享一个BookStore ios程序的例子
  3. C++ 外部函数通过指针修改类成员的值
  4. mysql-用正则表达式进行搜索
  5. win7个人计算机的ip地址,win7计算机ip地址查询_win7本机ip地址查询
  6. cocos2d精灵教程(三篇)
  7. MapReduce多用户任务调度器——容量调度器(Capacity Scheduler)原理和源码研究
  8. linux能运行英魂之刃吗,英魂之刃需要什么电脑配置
  9. php使用hset报错,hSet 命令/方法/函数
  10. 窗口大小改变时,显示内容的处理(正投影情况)
  11. 如何判断当前循环的栏目是不是最后一个
  12. 2440 OV9650 C通道保存图片完全成功!顶!
  13. Java获取压缩包内文件数_java获取递归获取嵌套压缩包(zip和rar)中的所有文件
  14. 分析docker启动MySQL挂载目录提示权限不足Permission denied原因
  15. matplotlib色彩(colors)之色彩基础知识(色彩模型,matplotlib色彩格式,matplotlib默认色彩映射)
  16. hackthebox - jail (考点:linux缓冲区 nfs配置提权 rvim提权 rar解密 rsa解密)
  17. oracle缩小数据文件大小,怎样将数据文件的大小变小
  18. 50、ubuntu18.0420.04+CUDA11.1+cudnn11.3+TensorRT7.2/8.6+Deepsteam5.1+vulkan环境搭建和YOLO5部署
  19. ATF官方文档翻译(二):Authentication Framework Chain of Trust(身份验证框架和信任链)(3)
  20. java开发的微信公众号服务端生产环境中的两个大坑

热门文章

  1. oracle一个lun多大,Oracle RAC中验证LUN_ID对应情况
  2. SpringMVC 日期类型转换
  3. leetcode 有效的数独
  4. CCF 201512-2 消除类游戏
  5. 【安卓开发】Webview简单使用
  6. C#LeetCode刷题之#551-学生出勤纪录 I​​​​​​​(Student Attendance Record I)
  7. C#LeetCode刷题之#189-旋转数组(Rotate Array)
  8. django新建一个项目_如何使用Django创建项目
  9. JavaScript高阶函数快速入门
  10. mysql管理应用_如何在PHP和MySQL中制作出色的库存管理应用程序