持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情
哈夫曼树编码
建树过程(以图为例)
step1: 权重1,2,3,4 ;选取权重最小的两个节点1,2 作为左右节点,已建一棵树(也可以看作一个大节点),
step2: 权重1,2,3,4,3,选取权重最小的两个节点3,3 建树(红色标记已选过)
step3:权重 1,2,3,4,3,6 选取权重最小的两个节点4,6 建树 当节点已经完了,建树完毕
哈夫曼树特点
(1)n个叶子结点的哈夫曼树共有2n-1个结点
(2)每一个字符都对应的是叶子节点。
(3)左右子树交换后仍是哈夫曼树
分析代码过程
1 抽象成员变量 结合上面手动建树过程,抽象哈夫曼树节点的成员变量:节点字符,权重,左右孩子节点,双亲节点。
2 结合文章梳理的思路 以前仅局限于在课本上手动构造哈夫曼树,现在要把他转换为代码,刚开始看还是有难度,但是仔细去读代码,分析,对自己的思维有很大的提升,希望下次自己能不看代码,自己分析写出来。
(1)读文本
给一串文本(文本内容仅只含ASCII码上的),读文本内容返回字符串内容。需要用到I/O输入输出流
(2)解析文本内容:
获取字符串内容,解析字符串并利用数组来存储,即包括字符存储,字符对应的权重(字符出现的个数),而要存储这两类值,需要借助了一个辅助数组(长度是ASCII码总个数,因为ASCII码十进制是从0开始的,所以就可以一一对应字符即强转)遍历字符串,将对应的字符出现的个数存储到这个辅助数组下,这样即可以知道有那些字符(索引值),又可以知道字符的权重(数组本身值)。结合这个数组即可对字符存储和字符对应权重的数组赋值了。(通过代码,在字符存储和字符权重存储两个数组,要一一对应,即字符a存储的数组索引是1,则a对应权重存储的数组索引也需要是1,否则这个顺序乱了,则产生的编码这些会出错。在思考是否可以用Map键值对来实现。)我认为最关键的就是这一步,解析文本内容。如下代码tempCharCounts就是这个辅助数组,如果他的值没有弄好就会导致后面一系列的操作会出现问题。
int[] charCounts; // 每一个字符所对应的权重
//tempCharIndex 字符对应的ASCII码的索引
//tempCharCounts 记录该字符的权重
for (int i = 0; i < inputText.length(); i++){
tempChar = inputText.charAt(i);
tempCharIndex = (int)tempChar;
System.out.println(" " + tempCharIndex + " ");
tempCharCounts[tempCharIndex]++;
}
(3)建树: 在解析文本内容后我们得到的以下两个数组是就建树的关键。其中通过循环来找最数组中权重最小的两个结点,自底向上建哈夫曼树。这里借助了boolean类型的tempProcessed数组来标记结点是否已经被选过。循环的开始和结束位置需要留意以下。
char[] alphabet; //解析文本所包含的字符(字符步重复出现)
int[] charCounts; // 每一个字符所对应的权重(数组长度是alphabetLength*2 -1)
HuffmanNode[] nodes; //存储新建的哈夫曼树
//建树的循环
for (int i = alphabetLength; i < alphabetLength*2 - 1; i++){
}
(4)生成哈夫曼编码:建树成功后,要对叶子节点上的每一个字符进行编码(左0右1)来产生编码,并存在一个字符数组中。在生成哈夫曼编码,利用了一个双重循环,外层循环是循环需要编码的字符个数,内层循环是对每一个字符从底向上编码(通过双亲结点向上),则打印的时候就是以我们正常的从上向下读的编码.
(5)编码:输入字符串,结合生成的哈夫曼编码的字符串数组 拼接成字符串即可
(6)解码:输入编码字符串,在解码的时候 我们就要从根开始去循环字符串编码,遇到0就往左子树走,遇到1就往右子树走,直到遇到的结点左子树为null(根据哈夫曼树的特点,只要左子树为null,不用判断右子树),说明第一个字符的编码走完,又重新回到根节点走下一个字符的编码。