Huffman树

Huffman树:以静态三叉链的存储结构建立的二叉树
Huffman树是一个带权路径长度最小的二叉树,又称最优二叉树

Huffman树的构造方法

①将每个结点都看作是一个树;
②选择两个根结点值最小的二叉树,构造一个新的二叉树,直至剩一个树为止。

Huffman树的存储(静态三叉链,n为叶子结点个数)

Huffman编码

前缀编码:对每一个字符规定一个0,1串作为其代码,并要求任一字符的代码都不是其它字符代码的前缀。
哈夫曼编码:依据权值构造哈夫曼树,根据此树得到字符集的二进制前缀编码。哈夫曼编码是一种不等长的二进制编码。其特点是字符的编码长度和字符的使用频率成反比。
如:

Huffman树实例

①建立存储结点
class HuffmanNode{//权重public int weight;//父结点public int pNode;//左孩子public int lChild;//右孩子public int rChild;public HuffmanNode() {this.weight = 0;this.pNode = this.lChild = this.rChild = -1;}
}
②预先定义
//ASCII码共256种字符private final int MAX_NUMBER = 256;//静态二叉树private HuffmanNode[] huffMan;//权重private int[] weight;//存储Huffman编码private String[] hfCode;
//初始化public HuffmanTree(){this.huffMan = new HuffmanNode[2*MAX_NUMBER - 1];this.weight = new int[MAX_NUMBER];this.hfCode = new String[MAX_NUMBER];}
③读入文件,计算权值

此处根据ASCII码来对权值进行计算,其静态二叉链长度为256*2-1

/*** 根据文件路径读入文件计算出权重信息* @param file*/public void file2Weight(String infile) {try {BufferedReader bReader = new BufferedReader(new FileReader(infile));int c;  //以ASCII码的形式接收字符while((c = bReader.read()) != -1) {weight[c]++;} bReader.close();} catch (Exception e) {e.printStackTrace();}  }
④根据权值构建Huffman树

这一步的重点就是在已有的树中找到两个权值最小的结点

/*** 构建哈夫曼树*/public void createHuffmanTree() {int index = 0;//将权重存入Huffman树for(; index< this.MAX_NUMBER; index++) {huffMan[index] = new HuffmanNode();huffMan[index].weight = weight[index];}//对Huffman树进行更新for(;index < 2*this.MAX_NUMBER-1; index++) {huffMan[index] = new HuffmanNode();//右子树等于左子树int rchild = getMinWeight(index);int lchild = getMinWeight(index);//设置父结点huffMan[rchild].pNode = index;huffMan[lchild].pNode = index;//创造父结点huffMan[index].weight = huffMan[rchild].weight + huffMan[lchild].weight;huffMan[index].lChild = lchild;huffMan[index].rChild = rchild;}}

从表中取出最小结点:

/*** 在已经存在的Huffman结点中找到最小的* @return 返回权重最小的结点*/private int getMinWeight(int stop) {int move = 0;int minWeight = 99999;       //赋予较大的值int min = 0;                    //记录最小的点while(move < stop) {if(huffMan[move].pNode == -1) {if(huffMan[move].weight < minWeight) {minWeight = huffMan[move].weight;min = move;}     } move++;}  //进行标记huffMan[min].pNode = -2; return min;}
⑤根据Huffman树得出Huffman编码

借助栈的特性,从叶子结点回溯到根结点,对回溯路径进行存储,再输出即编码

/*** 根据HuffMan树提取HuffMan码* 从数据端开始,向根回溯,用栈进行存储*/public void createHuffmanCode() {int satck[] = new int[this.MAX_NUMBER];int top = 0;int move,parent;for(int i=0; i < this.MAX_NUMBER; i++) {move = i;//之前我设置的默认父结点为-1,此处可用于判断while((parent = huffMan[move].pNode) != -1) {//左孩子为零,右孩子为一if(huffMan[parent].lChild == move) {satck[top++] = 0;}else {satck[top++] = 1;}move = parent; }//出栈,拼接(编码)StringBuilder sBuilder = new StringBuilder();for(int j = top-1; j >= 0; j--) {sBuilder.append(satck[j]);}top = 0;this.hfCode[i] = sBuilder.toString();}}
⑥再次遍历文件,将对应字符转为Huffman编码

在此处有一个问题:因为Huffman码是以String数组存储的,即每一位都是一个字符,占1个字节,8位的空间;而在转换过程中每一个字符都会变成一串编码,于是文件在压缩后并没有变小,反而变大了。
所以,我们要将每一位都变成一个字节类型,然后进行存储。
但是,java中并无直接以字节进行传输的IO流、于是乎,需将每8位字节转为一个byte类型,然后通过字节流的byte数组进行写入。此时要注意的是,字符串最后可能未满8位需在低位补1(防止100等直接转为0,导致数据丢失)。

/*** 对文件按照huffman编码进行编码,并写入文件* @param infile   源文件* @return 压缩文件地址*/public String condenseFile(String infile) {String newFilename = null;try {BufferedReader bReader = new BufferedReader(new FileReader(infile));int c;StringBuilder sBuilder = new StringBuilder();while((c = bReader.read()) != -1) {sBuilder.append(hfCode[c]);}byte[] data = new byte[sBuilder.toString().length()/8+1];int exceed = str2ByteArray(sBuilder.toString(), data);newFilename = infile.substring(0,infile.indexOf("."))+"press"+exceed+infile.substring(infile.indexOf("."));BufferedOutputStream boStream = new BufferedOutputStream(new FileOutputStream(newFilename));boStream.write(data); boStream.flush();bReader.close();boStream.close();} catch (Exception e) {e.printStackTrace();}  return newFilename;}

数据转换:此时还需注意byte是有符号类型,最高位为符号位,当数据末尾恰好满8位也应进行判断,否则会造成数组越界。

/*** 将字符串转为byte数组,并返回填充长度* @param str  字符串* @param data  byte数组* @return  填充长度*/private int str2ByteArray(String str,byte data[]) {//计算数组长度int result = 0;int cursor = 0; //设置游标char[] ch = new char[8];//每八位拼出一个,有符号byte,对于最后,若不足八位,后面添1for(int i=0; i < data.length-1; i++) {ch = str.substring(cursor, cursor+8).toCharArray();for(int j=1; j < ch.length; j++) {result += java.lang.Math.pow(2, 8-1-j)*(ch[j]-48);}//判断符号位if(ch[0]-48 == 1) {result = 0 - result;}data[i] = (byte)result;//移动游标,结果置零cursor = cursor + 8;result = 0;}//对于最后,若不足八位,后面添1(防止10变为0)if(cursor == str.length()) { //若恰好够八位data[data.length-1] = 0;return 8;   }else {ch = str.substring(cursor).toCharArray();for(int j=1; j < str.length()-cursor; j++) {result += java.lang.Math.pow(2, 8-1-j)*(ch[j]-48);}for(int k=str.length()-cursor; k<8; k++) {result += java.lang.Math.pow(2, 8-1-k);}if(ch[0]-48 == 1) { result = 0 - result;}data[data.length-1] = (byte)result;return 8-(str.length()-cursor);}  }

PS:在该处我使用了一个取巧的办法,将最后填补的位数放在了文件名中

⑦遍历压缩文件,根据Huffman树解码

解码方法:读文件会得到一个byte类型数据,然后转化为8位,取一位跟Huffman树做比对,0向左孩子走,1向右孩子走,到叶子结点就输出。
此处对文件名中的信息进行提取。

/*** 对压缩后的文件进行解压* @param unPressFile  解压目的地址*/public void recoverFile(String pressFile, String unPressFile) {try {BufferedWriter bWriter = new BufferedWriter(new FileWriter(unPressFile));BufferedInputStream biStream = new BufferedInputStream(new FileInputStream(pressFile));int size = Integer.parseInt(pressFile.substring(pressFile.indexOf("press")+5, pressFile.indexOf("press")+6));int c,judge;int move = 2*this.MAX_NUMBER - 2;  //从哈夫曼树的最顶端开始查询c = biStream.read();int length = 8;   //数组中存储的实际有用数据while(c != -1) {judge = biStream.read();//判断是否为结尾if(judge == -1) {length = length - size;}c = (byte)c;int[] data= byte2Int(c);//需先走树再判断,不然最后一位取不到for (int i = 0; i < length; i++) {//0走左树,1走右树if(data[i] == 0) {move = huffMan[move].lChild;}else {move = huffMan[move].rChild;}if(huffMan[move].lChild == -1 && huffMan[move].rChild == -1 ) {bWriter.write((char)move);bWriter.flush();move = 2*this.MAX_NUMBER - 2; //重置     }}c = judge;}biStream.close();bWriter.close();} catch (Exception e) {e.printStackTrace();}}

解压文件时数据转换:即将byte类型转换为01码```

/*** 将一个byte长的数转为二进制* @param num byte数* @return int数组*/public int[] byte2Int(int num) {//返回结果int[] result = {0,0,0,0,0,0,0,0};//栈int[] stack = {-1,-1,-1,-1,-1,-1,-1,-1};int top = 0;//有符号if(num < 0) {result[0] = 1;num = 0 - num;}//入栈while(num != 0) {stack[top++] = num%2;num = num/2;}//将栈中数据存入结果数组int move = 7;   //结果数组游标for(int i=0; i < top; i++) {result[move--] = stack[i];} return result;}
⑧主函数测试
 HuffmanTree hfTree = new HuffmanTree();String inFile = "Test/Hufin.txt";String pressFile;String unPressFile= "Test/Huffmanunpress.txt";//①建立权值表hfTree.file2Weight(inFile);//②建立Huffman树hfTree.createHuffmanTree();//③求得huffman编码hfTree.createHuffmanCode();  //④根据编码压缩文件pressFile = hfTree.condenseFile(inFile);System.out.println("压缩后地址为:"+pressFile);//⑤根据编码解压文件hfTree.recoverFile(pressFile,unPressFile);

测试情况:
控制台输出:

文件内容比对:

文件大小比对:

Huffman树压缩和解压文件相关推荐

  1. Qt qCompress和qUncompress 压缩和解压文件

    利用Qt的qCompress和qUncompress来压缩和解压文件 有个特点,用qCompress压缩的文件不能直接用别的软件来解压,需要经过处理,否则只能利用Qt的qUncompress来解压,因 ...

  2. JAVA 7z Seven Zip 压缩和解压文件

    JAVA 7z Seven Zip 压缩和解压文件 7-Zip是基于GNU LGPL协议发布的软件,通过全新算法使压缩比率大幅提升 本文主要讲解通过JAVA方式把文件压缩成7z文件和对7z文件进行解压 ...

  3. mac 命令行 解压7z文件_如何在Mac上快速压缩和解压文件?Mac上解压和压缩文件的方法...

    苹果mac电脑怎么压缩和解压文件?Mac电脑仅默认支持把文件压缩成zip格式,解压成zip.tar.gz,bz2等格式,有些操作需要安装第三方软件来完成,这篇文章为大家带来几种关于在Mac上解压和压缩 ...

  4. Linux系统-gzip命令 – 压缩和解压文件

    gzip命令来自于英文单词gunzip的缩写,其功能是用于压缩和解压文件.gzip是一款使用广泛的压缩工具,文件经过压缩后一般会以.gz后缀结尾,与tar命令合用后即为.tar.gz后缀. 据统计,g ...

  5. 总结Linux系统压缩和解压文件指令——gzip/gunzip 指令、zip/unzip 指令、tar 指令

    Linux系统压缩和解压文件指令 gzip/gunzip 指令:gzip 用于压缩文件, gunzip 用于解压的 基本语法 应用实例 细节说明 zip/unzip 指令:zip 用于压缩文件, un ...

  6. WinRAR 分卷压缩和解压文件

    WinRAR 分卷压缩和解压文件 1. WinRAR http://www.winrar.com.cn/ 1.1 分卷压缩文件 文件 -> 添加到压缩文件 -> 切分为分卷 (V),大小 ...

  7. 如何在Mac上快速压缩和解压文件?Mac上解压和压缩文件的方法

    苹果mac电脑怎么压缩和解压文件?Mac电脑仅默认支持把文件压缩成zip格式,解压成zip.tar.gz,bz2等格式,有些操作需要安装第三方软件来完成,这篇文章为大家带来几种关于在Mac上解压和压缩 ...

  8. 用SharpZipLib来压缩和解压文件 --zt

    from:http://www.cnblogs.com/zhangweiguo3984/archive/2007/03/15/314333.html#675634 1.建立工程,添加引用,添加Shar ...

  9. linux bzip2压缩文件,bzip2命令_Linux bzip2命令:压缩和解压文件(.bz2文件)

    有时候你会发现并不是所有的 Linux 压缩包都是以 .tar.gz 为后缀的,有些压缩包的后缀是 .tar.bz2.这个 .tar.bz2 又是什么呢?它就是本文的主角:bzip2 压缩工具. 有了 ...

最新文章

  1. amd同步多线程_AMD下一代锐龙APU实锤!Zen3、RDNA2绝配
  2. html自定义标签提示,用简单的jquery+CSS创建自定义的a标签title提示tooltip_HTML/Xhtml_网页制作...
  3. 区块链BaaS云服务(7)微软Azure区块链服务
  4. 按某列获取几行_机器学习获取数据难?别忘记特征工程
  5. 即插即用的轻量注意力机制ECA--Net
  6. centos 8 rpm yum install_关于yum不能正常使用的解决方案
  7. Microsoft Access、MySQL 以及 SQL Server 所使用的数据类型和范围。
  8. One question regarding your note Note 1731777 - Debugging background work items
  9. andpods授权码订单号分享_微信OAuth2授权登录
  10. Java 8 函数式编程学习笔记
  11. 实用工具推荐:LICEcap(屏幕录制.gif)
  12. 关于SDWebImage
  13. 100行JS代码实现❤坦克大战js小游戏源码 HTML5坦克大战游戏代码(HTML+CSS+JavaScript )...
  14. 站长必会数据统计工具教程:百度统计 VS GA
  15. 软件测评师的一些重点①
  16. AI后门检测论文翻译:Universal Litmus Patterns: Revealing Backdoor Attacks in CNNs
  17. VirtualBox AndroidX86 网络设置
  18. C# Dictionary多线程安全访问问题
  19. oracle查运行sql语句,查询Oracle正在执行的SQL语句
  20. cocos Creator 3.2 关于 NodePool 对象池的应用- (弹出框)

热门文章

  1. BGP——权重选路(讲解+配置命令)
  2. 【问题记录】 Linux分区磁盘占满,导致ssh登陆闪退
  3. 从厕所排队引发的产品设计方案思考
  4. 深入Redis客户端(redis客户端属性、redis缓冲区、关闭redis客户端)
  5. java 课后习题 温度转换
  6. 微信小程序request:fail invalid url
  7. 【Linux】在Deepin v20或UOS20下运行MC我的世界
  8. C#LeetCode刷题之#811-子域名访问计数​​​​​​​(Subdomain Visit Count)
  9. Redux中的功能式React式编程简介
  10. 编写react组件_如何编写第一个React.js组件