深入理解 哈夫曼编码
来源:互联网 发布:阿里云服务器亚马逊 编辑:程序博客网 时间:2024/05/21 08:04
哈夫曼编码(Huffman Coding)
——牛客<美团点评>笔试【编程题】 字符编码
最近,春招实习生开始了,相信大家都开始准备各种面试、笔试了。
博主也不例外,走上了起早贪黑,看书、刷题的不归路。作为人生的第一篇博客(之前确实太懒),在这里给大家带来一篇 哈夫曼编码 相关知识的讲解与代码分享,同时也作为一个算法笔记,有兴趣的同学可以来看看,有写的不对的地方欢迎指出,祝大家都能拿到一个好的offer。
首先是 哈夫曼编码 的一些理论基础
哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的带权路径长度记为WPL=(W1*L1+W2*L2+W3*L3+…+ Wn*Ln),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明哈夫曼树的WPL是最小的。
构造哈夫曼树的算法如下:
1)对给定的n个权值{W1,W2,W3,…,Wi,…,Wn}构成n棵二叉树的初始集合F={T1,T2,T3,…,Ti,…, Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。
2)在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
3)从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
4)重复2)和3),直到集合F中只有一棵二叉树为止。
简而言之,就是要先知道一系列的权值信息(比如说要编码字符串的每个字符出现频率),然后把它们分别作为哈夫曼树中各个叶子节点的权值,接下来呢就需要由叶子节点来自下往上构造哈夫曼树了。
构造的思路是这样的:所有的叶子节点构成了最初的森林(所有树的集合),我们可以使用priority_queue对这些树进行管理,使得集合中的数据始终保持着递增的顺序。然后,每次就让前两个元素出队,再构造一个新的节点(树),其权值为出队的两个元素权值之和,左子树为前面第一个元素,右子树为前面第二个元素,这样集合中元素的个数-1。如此做,直至集合中只剩余一个节点(根节点)为止,此时仅有的这棵树便是哈夫曼树。
以美团的这题为例
输入
MT-TECH-TEAM
输出
33
具体编码流程如下
至此,哈夫曼树完成构造,接下来只需要遍历统计即可。
附上具体实现代码
#include<iostream>#include<stdlib.h>#include<string.h>#include<stdio.h>#include<algorithm>using namespace std;/**哈夫曼编码思想**/typedef struct Node{ char typeChar; int rate; Node *leftChild; Node *rightChild;}charNode;//存放输入的字符种类char *inputStr;//存放字符节点的数组Node nodes[200 + 199];//总的节点数int total;//记录当前所处的深度int depth;//统计数量int getNum(char *in, char c){ int times = 0; for (int i = 0; i <= strlen(in); i++) { if (*(in + i) == c) times++; } return times;}//统计输入的字符种类(当时写的这方法不好,个人推荐使用hash存储的方式统计)int getTypeChar(char *in){ inputStr = (char *)malloc(sizeof(char)*200); memset(inputStr, '\0', sizeof(inputStr)); int p = 0; bool isAdded; //cout<<strlen(in)<<endl; for (int i = 0; i<strlen(in); i++) { isAdded = false; if (p == 0) { *inputStr = *in; nodes[p].typeChar = *inputStr; nodes[p].rate = getNum(in, *in); nodes[p].leftChild = NULL; //叶节点 nodes[p].rightChild = NULL; //叶节点 p++; continue; } for (int j = 0; j<p; j++) { if (*(inputStr + j) == *(in + i)) { isAdded = true; break; } } if (!isAdded) { inputStr[p] = *(in + i); nodes[p].typeChar = *(inputStr + p); nodes[p].rate = getNum(in, *(in + i)); nodes[p].leftChild = NULL; //叶节点 nodes[p].rightChild = NULL; //叶节点 p++; } } return p;}//给排序函数使用的比较函数bool compNode(const Node & node1, const Node & node2){ if (node1.rate <= node2.rate) { return true; } else { return false; }}//哈夫曼编码void huf(int startIndex, int endIndex, int length){ sort(nodes + startIndex, nodes + startIndex + length, compNode); nodes[endIndex + 1].rate = nodes[startIndex].rate + nodes[startIndex+1].rate; nodes[endIndex + 1].leftChild = &nodes[startIndex]; nodes[endIndex + 1].rightChild = &nodes[startIndex+1]; if (endIndex + 1 != total) { huf(startIndex + 2, endIndex + 1, length - 1); }}int depthFirst(charNode *);//遍历哈夫曼树,计算出编编码长度int getCodeLength(int lastIndex){ charNode *root = &nodes[lastIndex]; depth = -1; return depthFirst(root);}//递归遍历int depthFirst(charNode *node){ depth++; if (node->leftChild == NULL && node->rightChild == NULL) { return node->rate * depth; } else { int leftSize = depthFirst(node->leftChild); depth--; int rightSize = depthFirst(node->rightChild); depth--; return leftSize + rightSize; }}int main(){ char *in; int num; //不同字符的数量 in = (char *)malloc(sizeof(char) * 1000); while(scanf("%s", in) != EOF) { num = getTypeChar(in); if(num == 1) { cout<<strlen(in)<<endl; continue; } total = num * 2 - 1; huf(0, num - 1, num); cout<<getCodeLength(total-1)<<endl; } return 0;}
如果只是为了AC也不需要写这么复杂。不过此处主要考虑到对 哈夫曼编码 的理解,所以代码方面就自己构造结构体、指针去实现了
另外代码中用到的二叉树一个重要性质
对于任意二叉树,设拥有叶子结点a个,二度节点b个,则满足:
- 深入理解 哈夫曼编码
- 深入理解“编码”
- 深入理解java编码
- python字符集编码深入理解
- 深入理解vim编码设置
- java中编码深入理解
- 深入理解C语言-02-数据编码
- 深入理解LINUX中的字符编码
- 深入理解计算机系统之字符编码
- 深入理解编码字符的发展史
- ANSI编码和Unicode编码之深入理解
- ANSI编码和Unicode编码之深入理解
- 哈夫曼编码理解
- 黑马程序员【深入理解IO流中字符编码问题】
- 深入理解java核心卷轴---编码utf-8
- 深入理解Python的字符编码utf-8 & unicode
- 深入理解-字符编码ASCII,GB2312,GBK,Unicode,UTF-8
- 深入理解Python中的字符编码、文件处理和函数
- uva10285
- Java对各种排序算法的实现
- GET和POST本质区别
- 编程英文单词的标准缩写
- 关于 iOS 开发者账号
- 深入理解 哈夫曼编码
- MIT的《深度学习》精读(7)
- 经典排序之冒泡排序
- python操作mysql
- Android之数据解析
- c#ComboBox的联动
- 20条超级有用的Java程序片段
- Bean Validation和Hibernate Validator使用小记
- mybatis上传和下载文件