哈夫曼编码是基于哈夫曼树实现的。而哈夫曼树又是基于完全二叉树实现的。那么什么是完全二叉树呢?
完全二叉树(Complete Binary Tree)
若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
完全二叉树是由满二叉树而引出来的。对于深度为K的,有N个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。
若一棵二叉树至多只有最下面的两层上的结点的度数可以小于2,并且最下层上的结点都集中在该层最左边的若干位置上,则此二叉树成为完全二叉树。《来自百度百科》
(如果对二叉树不了解的可以去看我博客写的二叉树数据结构)
那么哈夫曼编码到底有什么作用呢? 在远距离数据通信中,需要将文字转换成二进制的字符串,用0,1码的不同排列来表示字符串。例如需要传送报文“good good study”,用到了8个字符:g:2次;o:4次;d:3次;空格:2次;s,t,u,y各1次,现在要为这些字母设计编码,最简单的二进制编码是等长 编码,由于只用到8个字符,只要用3位二进制编码即可区别,共需要传输(2+4+3+4*1+2)*3=45个二进制位,然而问题是:在实际中,我们往往 更希望报文长度尽可能的短,那么是否有一种编码方式能够实现所得报文长度最短呢?当然有,即:哈夫曼编码。
哈夫曼编码是怎么实现的呢?看下面图,你大概会了解什么叫-----编码唯一、编码最短。
哈夫曼编码的核心思想就是:使用频率高的字符编码最短,使用频率低的字符编码编码最长,这样就可以使得树的带权值路径长度最小,即用最短0、1 序列传输信息。
好了说了这么多,那么我们来分析下怎么去实现这样编码唯一,编码最短的哈夫曼编码呢?
首先我们要给每个字母根据频率建哈夫曼树。所有的节点Node{}里面有数据域权值(频率)(int weight),左右孩子节点Node lchild,Node rchild,双亲节点Node parent,即用二叉链表的形式构建哈夫曼树,权值小的(使用频率低的)字符要在最长路径,所以我们采用逆向建树的过程。
怎么逆向建树呢?
我们需要辅助数组来帮助我们建树。申请一个长度为2*n-1的Node 型数组来存储所有将要创建的哈夫曼树的所有节点(想想为什么需要2*n-1长度,或者节点总数为什么是2*n-1?)首先把所有已知的叶节点存入数组,然后每次都去选取parent == null的权值最小的节点来创建一个子树,比如说上图中选取权值2、3的节点创建新的节点(5--<2,3>),它的lchild是权值为2的节点,rchild是权值为3的节点,并且将这个节点append到数组后面。(注意:创建新节点的过程要把权值为2和3的Node.parent = new Node(),并且new Node的权值应该为Node(2).weight+Node(3),weight)以此类推,数组最后一个空间!=null为止,即根节点的产生。
建完树之后怎么求编码?
举个例子比如说a这个字符怎么求它的哈夫曼编码呢?我们这里采用逆向寻找根节点,首先申请一个空字符串str,利用辅助数组,去寻找叶节点的root,然后每次寻找节点的parent的时候,判断该节点是该节点parent的lchild还是rchild,如果是lchild,str.append("0"),rchild,str.append("1")寻找到root之后str存储的字符串为a字符哈夫曼编码的逆序,聪明的你一定可以把它变成正序的(当然你可以选择二叉树的遍历,来寻找节点值为a的那个节点,首先定义一个空字符串str,然后每次选择llchild时候str.append("0"),rchild时候str.append("1"),这里str是a字符的哈夫曼编码正序)。
下面我来拓展介绍下哈夫曼编码的编码器和译码器:
编码器:其实在上面求每个字符哈夫曼编码的时候,我们可以把这些编码存到一个数据结构中,然后根据用户输入的字符串挨个解析字符输出每个字符对应的哈夫曼编码,将它们按照顺序输出就可以;
译码器:用户输入的是0、1序列,回顾下我们之前在求字符编码的时候0、1分别代表什么。0是代表lchild,1代表rchild,那么根据用户输入的0、1序列,从root节点,读到0就往左子树找,读到1就往右子树找,一直到node.lchild==null&&node.rchild==null,即叶节点为止,输出对应的字符;然后又从root节点找,直到读完所有0、1序列,那么你就成功译码了!!
说了这么多,代码已经迫不及待了!~
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
class HuffNode {
char ch;
int weight;
HuffNode parent;
HuffNode lchild, rchild;
public HuffNode() {
}
public HuffNode(char ch, int weight, HuffNode parent, HuffNode lchild,
HuffNode rchild) {
this.ch = ch;
this.lchild = lchild;
this.rchild = rchild;
this.weight = weight;
this.parent = parent;
}
}
public class HuffCode {
public static void main(String[] args) throws FileNotFoundException {
char letter = 'a';
// 为26字母个以及空格符创建Huffman树申请空间 O = 27*2-1
HuffNode[] hfArr = new HuffNode[53];
// 第一个为空格字符
hfArr[0] = new HuffNode(' ', 184, null, null, null);
// 用文本io方式读取剩下字符和权值
File wgTable = new File("DataTable\\charTable.txt");
Scanner read = new Scanner(wgTable);
for (int i = 1; i <= 26; i++) {
hfArr[i] = new HuffNode((char) (i + 96), read.nextInt(), null,
null, null);
}
// 初始化剩下的空间
for (int i = 27; i <= 52; i++) {
hfArr[i] = new HuffNode();
}
// 构建Huffman树
int size = 26;
int minWeight;
int minIndex = -1;
int sminWeight;
int sminIndex = -1;
while (hfArr[52].lchild == null) {
minWeight = 10000;
sminWeight = 10000;
// 寻找没有双亲节点的权值最小的节点
for (int i = 0; i <= size; i++) {
if (minWeight > hfArr[i].weight && hfArr[i].parent == null) {
minWeight = hfArr[i].weight;
minIndex = i;
}
}
// System.out.println("找到最小值 " + minWeight + "字母为 "
// + hfArr[minIndex].ch);
// 寻找没有双亲节点的权值次小的节点
for (int i = 0; i <= size; i++) {
if (sminWeight > hfArr[i].weight && hfArr[i].parent == null
&& i != minIndex) {
sminWeight = hfArr[i].weight;
sminIndex = i;
}
}
// System.out.println("找次=最小值 " + sminWeight + "字母为 "
// + hfArr[sminIndex].ch);
// 两个权值最小的节点创建新节点
int weight = hfArr[minIndex].weight + hfArr[sminIndex].weight;
hfArr[++size] = new HuffNode('\0', weight, null, hfArr[minIndex],
hfArr[sminIndex]);
hfArr[minIndex].parent = hfArr[size];
hfArr[sminIndex].parent = hfArr[size];
}
// System.out.println(hfArr[52].parent);
// 求各个字符Huffman编码
HuffNode curhfNode;
String[] hfcode = new String[27]; // 申请27空间存储Huffman逆向编码(!!不是正向编码)
// 初始化每个字符串编码
for (int i = 0; i < hfcode.length; i++) {
hfcode[i] = "";
}
// 从子叶到根节点逆向求每个字符编码
for (int i = 0; i < hfcode.length; i++) {
// 当前叶节点
curhfNode = hfArr[i];
// 一直寻找,直到寻找到根节点
while (curhfNode.parent != null) {
// 假如是左孩子,则编码追加字符0,右孩子则追加字符1;
if (curhfNode.parent.lchild == curhfNode) {
hfcode[i] += "0";
} else {
hfcode[i] += "1";
}
curhfNode = curhfNode.parent;
}
}
// System.out.println("a = " + hfcode[1]);
// 对每个字符的Huffman逆向编码倒转成正向编码
String tempStr;
for (int i = 0; i < hfcode.length; i++) {
tempStr = "";
for (int j = hfcode[i].length() - 1; j >= 0; j--) {
tempStr += hfcode[i].charAt(j);
}
hfcode[i] = tempStr;
}
char op;
System.out.println("Which do you want to do? Encoding or Decoding? The former choose \"A\" and the latter choose \"B\"");
Scanner choose = new Scanner(System.in);
op = choose.next().charAt(0);
if(op == 'A'){
encode(hfcode);
}else{
decode(hfArr[52]);
}
}
/**
* 编码器原理:读取每一个字符,通过寻找该字符在Huffman编码表中所对应哈夫曼编码,来依次append编码就可以
* @param hfcode
*/
public static void encode(String[] hfcode){
System.out.println("Please enter your Plain Text: ");
Scanner cin = new Scanner(System.in);
String code = cin.nextLine();
String otCode = "";
for(int i =0;i<code.length();i++){
if(code.charAt(i)==' '){
otCode+=hfcode[0];
}else{
otCode+=hfcode[code.charAt(i)-96];
}
}
System.out.println("After encoding: "+otCode);
}
/**
* 译码器原理:主要是通过读取0,1序列(0代表左孩子,1表示右孩子)从根节点开始寻找对应的叶节点,每次寻找到叶节点(即字符)就又从根节点开始寻找
* @param rootHf
*/
public static void decode(HuffNode rootHf){
HuffNode tempHuf = rootHf;
System.out.println("Please enter your Cipher Text: ");
Scanner cin = new Scanner(System.in);
String code = cin.nextLine();
String otText = "";
for(int i=0;i<code.length();i++){
//读到1.就寻找右孩子
if(code.charAt(i)=='1'){
tempHuf = tempHuf.rchild;
//寻找到对应的字符就重新从根节点开始
if(tempHuf.ch>='a'&&tempHuf.ch<='z'||tempHuf.ch==' '){
otText+=tempHuf.ch;
tempHuf = rootHf;
}
//读到0,寻找左孩子
}else{
tempHuf = tempHuf.lchild;
//寻找到对应的字符就重新从根节点开始
if(tempHuf.ch>='a'&&tempHuf.ch<='z'||tempHuf.ch==' '){
otText+=tempHuf.ch;
tempHuf = rootHf;
}
}
}
System.out.println("After decoding: "+otText);
}
}
- 大小: 102.3 KB
分享到:
相关推荐
### 哈夫曼编码的贪心算法设计 #### 实验背景与意义 哈夫曼编码是一种广泛应用的数据压缩技术,特别是在文件压缩领域有着极其重要的作用。哈夫曼编码利用了贪心算法的思想来构建最优的前缀编码树,进而达到高效...
哈夫曼树与哈夫曼编码是数据结构和算法领域中的一个重要概念,广泛应用于数据压缩、文本编码以及优先队列等场景。哈夫曼编码是一种特殊的前缀编码方法,能够为字符提供一种高效的二进制表示,使得频繁出现的字符具有...
哈夫曼编码计算信源熵及编码效率 哈夫曼编码是一种变长前缀编码方法,它可以根据信源符号的概率分布计算信源熵和编码效率。本文将详细介绍哈夫曼编码的原理、步骤和实现方法。 哈夫曼编码的原理 哈夫曼编码的原理...
哈夫曼编码是一种高效的数据编码方法,主要用于无损数据压缩。它的原理是为每个字符分配一个唯一的二进制编码,使得频繁出现的字符拥有较短的编码,而不常出现的字符则有较长的编码,以此来减少平均编码长度,达到...
哈夫曼编码是一种高效的数据编码方法,主要用于无损数据压缩,尤其在文本和图像处理领域广泛应用。它基于字符出现频率构建最优的二叉树结构,使得频繁出现的字符占用更短的编码,从而提高压缩效率。这个“图像处理...
### 哈夫曼编码MATLAB程序解析 #### 前言 哈夫曼编码是一种广泛应用在数据压缩领域的编码方式,其核心思想是通过构建一棵特殊的二叉树(哈夫曼树)来实现对不同字符的高效编码。该编码方法在确保原始数据可被完全...
哈夫曼编码是一种高效的数据压缩方法,主要用于无损数据压缩,尤其在文本文件的压缩中效果显著。它基于字符出现频率构建最小带权路径长度(Minimum Weighted Path Length, MWPL)的二叉树,也被称为哈夫曼树。在这个...
哈夫曼编码是一种高效的数据压缩方法,由大卫·艾伦·哈夫曼在1952年提出。它是基于字符频率构建的一种前缀编码,能够为频繁出现的字符分配较短的编码,从而减少数据存储空间,提高传输效率。在C语言中实现哈夫曼...
哈夫曼编码是一种高效的数据压缩方法,源自于数据结构中的二叉树理论,由David A. Huffman在1952年提出。它基于频率最小的编码原则,通过构建一棵特殊的二叉树(哈夫曼树)来为每个字符或符号生成唯一的前缀编码,...
哈夫曼编码是广泛用于数据文件压缩的十分有效的编码方式,其压缩率通常在20%—90%之间。哈夫曼编码算法是通过使用字符在文件中出现的频率表来构造最优前缀码的贪心算法。所谓前缀码,即是任一字符的编码都不是其他...
哈夫曼编码是一种高效的前缀编码方法,它根据数据出现频率分配最短的二进制码,频繁出现的字符拥有较短的编码,从而减少传输或存储的数据量。 实验报告中提到的实验目的是为了让学生熟练掌握树形结构,特别是哈夫曼...
哈夫曼编码是一种高效的数据压缩方法,常用于文本、图像等多种数据类型的压缩。在数据结构课程中,哈夫曼树(又称最优二叉树)是理解哈夫曼编码的关键。这个C语言实现的哈夫曼编码译码器是学习哈夫曼编码原理和实践...
"三元哈夫曼编码 哈夫曼树" 哈夫曼树是一种特殊的二叉树结构,它可以用于数据压缩、图像处理和网络通讯等领域。哈夫曼树的构造方法是根据给定的权值来构造一棵二叉树,使其带权路径长度 WPL 最小。哈夫曼树的优点是...
### 哈夫曼编码原理及其实现 #### 前言 哈夫曼编码是一种广泛应用在数据压缩领域的编码方式,特别适用于无损压缩。它通过构建一个特殊的二叉树——哈夫曼树来实现最优前缀编码。本文将详细介绍哈夫曼编码的基本...
哈夫曼编码是一种基于二叉树的数据压缩方法,由美国计算机科学家戴维·哈夫曼在1952年提出。在本项目中,我们利用哈夫曼编码实现了对文本文件的加密和解密功能,具体操作是针对ASCII字符集内的字符进行。以下是关于...
哈夫曼编码是一种高效的数据编码方法,主要用于无损数据压缩,通过构建最优的二叉树结构(哈夫曼树)来实现对字符的编码。在本项目中,使用Java语言实现了一个哈夫曼编码和解码的可视化界面,使得用户能够直观地观察...
哈夫曼编码实验报告 数据结构大作业哈夫曼编码实验报告是一个关于哈夫曼编码的实验报告,讲述了哈夫曼编码的基本原理、构造过程和代码实现。哈夫曼编码是一种变长前缀编码,用于数据压缩和编码。下面是该实验报告的...
哈夫曼编码是一种高效的数据压缩方法,源自于数据结构中的二叉树理论,由David A. Huffman在1952年提出。它基于频率优先的原则,通过构建最优的二叉树(也称为哈夫曼树或最小带权路径长度树)来为字符或符号分配唯一...
哈夫曼编码是一种高效的数据压缩方法,它基于贪心策略,是信息编码理论中的一个重要概念。在本实验中,我们将深入探讨如何实现哈夫曼编码,并对其进行算法复杂性的分析。 哈夫曼编码的基本原理是为每个字符或符号...