文件压缩(一)——Huffman树的构建

在传输文件的过程中我们经常会先对文件进行压缩,然后再传输。等接收到文件后,在进行解压缩。于是也打算自己动手写一个简单的文件压缩程序,大致分为三个过程,Huffman树的构建,利用哈夫曼树对输入的字符串进行压缩和解压缩处理,文件处理。今天,我们先来实现Huffman树的构建。在动手写代码之前我们先做一些准备工作。(这里不会详细介绍Huffman树,对Huffman基本概念不了解的同学请自行查阅其他资料)

一、Huffman树是什么

Huffman树又被称为“最优二叉树”。它是带权路径长度最短的树,权值较大的结点离根较近(这样子,出现频率较高的节点相应的编码长度就会越短)。

Huffman树的一个应用就是压缩文件。在计算机中字符所采用的编码是Ascii码,总共有八个bit位,可以表示256个字符。但是在实际生活中,我们的文本值用到这些字符当中的一小部分。那么此时这种编码方式就显得很浪费空间,传输起来肯定就比较慢。于是我们就想利用Huffman树重新对这些字符进行编码,以减少传输文本的大小,提高传输的速度。

二、构建思路(压缩文本以英文为例)

1.节点类需要包含哪些信息

A.数据(这里是字符);B.权值;C.左孩子节点;D.右孩子节点;E.Huffman编码

2.构建节点类

遍历带压缩的字符串,为出现的所有字符构建节点类,添加到节点数组nodes中;

3.构建树

遍历节点数组,每次都找出其中权值最小的两个节点,并为他们构建一个父节点,这个父节点的权值是这两个节点的权值之和,数据为空,左孩子是权值较小的那个节点,右孩子是权值较大的那个节点。终止条件是当nodes中只剩一个节点时。

4.获取字符编码

根据所构建的树,翻译出各个字符所对应的Huffman编码。在建树结束之后再去遍历整棵树,然后得到相应的编码。我这里的为每个节点新增了一个编码code,这个code被初始化为"”。这样我们在从根节点往下遍历时,遇到根节点的左孩子节点,就设置它的编码为其父节点的编码+“0”;右孩子节点,则设置它的编码为其父节点的编码+“1”,最后我们再把其中的叶节点的字符和编码添加到HashMap里面。

三、具体代码

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Queue;

//构造Huffman树
public class Huffman {
	//构造节点类
	public static class Node<E>{
		E data;//数据,这里是字符
		double weight;//权值
		String code="";
		Node leftChild;
		Node rightChild;
		
		public Node(E data,double weight) {
			this.data=data;
			this.weight=weight;
		}
	}
	
	public static void main(String[] args) {
		List<Node> nodes=new ArrayList<Node>();
		//这里只能用泛型的类型数据,比如int只能用Integer,这里的char只能用Character来代替
		HashMap<Character,String> map=new HashMap<Character,String>();
		
		nodes.add(new Node('a',40.0));
		nodes.add(new Node('b',8.0));
		nodes.add(new Node('c',10.0));
		nodes.add(new Node('d',30.0));
		nodes.add(new Node('e',10.0));
		nodes.add(new Node('f',2.0));
		
		Node root=Huffman.creatTree(nodes);//调用建树函数
		//打印出所有的节点
		List<Node> list=new ArrayList<Node>();
		list=Huffman.breadthFirst(root);
		
		for(int i=0;i<list.size();i++) {
			//判断字符是否存在,若存在则将其加入到Hashmap中
			if(list.get(i).data!=null) {
				map.put((Character) list.get(i).data, list.get(i).code);
			}
		}
		System.out.println(map);
	}
	
	/*
	 * 构造哈夫曼树树
	 * 通过插入节点数组来建树
	 */
	//建树函数
	public static Node creatTree(List<Node>nodes) {
		//判断数组中是否还有两个以上的节点
		while(nodes.size()>1) {
			Node minleft=nodes.get(0);
			int position=0;
			for(int i=1;i<nodes.size();i++)
				if(minleft.weight>nodes.get(i).weight) {
					 minleft=nodes.get(i);//找到最小值
					 position=i;
				}
			nodes.remove(position);//移除最小值
			
			Node minright=nodes.get(0);
			position=0;
			for(int i=1;i<nodes.size();i++) {
				if(minright.weight>nodes.get(i).weight) {
					minright=nodes.get(i);//找到第二个最小值
					position=i;
				}
			}
			nodes.remove(position);//移除最小值
			
			Node parent=new Node(null,minleft.weight+minright.weight);//构造父节点
			//设置孩子节点的指针
			parent.leftChild=minleft;
			parent.rightChild=minright;
			//把新建的父节点加到数组中
			nodes.add(parent);
		}
		return nodes.get(0);
	}
	
	//从根节点往下走,获取各个叶子结点的编码
	public static List<Node> breadthFirst(Node root){
		Queue<Node> queue=new ArrayDeque<Node>();//创建一个队列
		List<Node> list=new ArrayList<Node>();//创建一个数组
		
		//如果根节点不为空,就将它加入队列
		if(root!=null) {
			queue.offer(root);
		}
		while(!queue.isEmpty()) {
			//将队位元素加到list数组中
			list.add(queue.peek());
			/*
			 * java 堆栈中的方法poll和pop区别如下:
             *pop:相当于get的操作,就是只是查看。从此列表所表示的堆栈处弹出一个元素。
             *poll:相当于先get然后再remove掉,就是查看的同时,也将这个元素从容器中删除掉。 获取并移除此列表的头(第一个元素)
			 */
			Node p=queue.poll();
			
			//如果左孩子节点不为null,则入队列
			if(p.leftChild!=null) {
				//左孩子节点的编码等于父节点+0
				p.leftChild.code=p.code+"0";
				queue.offer(p.leftChild);
			}
			
			//如果右孩子节点不为空,则右孩子节点入队列
			if(p.rightChild!=null) {
				//右孩子节点的编码等于父节点+1
				p.rightChild.code=p.code+"1";
				queue.offer(p.rightChild);
			}
			
		}
		return list;	
	}
	
}

四、运行结果

五、总结反思

1.<>里面只能放泛型数据类型,而不能放具体的数据类型。比如int就只能用Integer,char只能用Character

2. java 堆栈中的方法poll和pop区别:
pop:相当于get的操作,就是只是查看。从此列表所表示的堆栈处弹出一个元素。

poll:相当于先get然后再remove掉,就是查看的同时,也将这个元素从容器中删除掉。 获取并移除此列表的头(第一个元素)

3.String最好初始为=“”;如果初始为null,后面如果要把这个字符串和其他字符串相连接,比如data=null,那么data+"12"就会变成“null12”

 

如何利用建好的Huffman树对字符进行压缩,可以看第二篇博客:文件压缩(二)——英文字符串的处理

综合实验: 1. 问题描述 利用哈夫曼编码进行通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。这要求在发送端通过一个编码系统对待传输数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站编写一个哈夫曼码的编/译码系统。 2. 基本要求 一个完整的系统应具有以下功能: (1) I:初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼,并将它存于文件hfmTree中。 (2) E:编码(Encoding)。利用已建好的哈夫曼(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。 (3) D:译码(Decoding)。利用已建好的哈夫曼文件CodeFile中的代码进行译码,结果存入文件Textfile中。 (4) P:印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码文件写入文件CodePrin中。 (5) T:印哈夫曼(Tree printing)。将已在内存中的哈夫曼以直观的方式(比如)显示在终端上,同时将此字符形式的哈夫曼写入文件TreePrint 中。 3. 测试数据 用下表给出的字符集和频度的实际统计数据建立哈夫曼,并实现以下报文的编码和译码:“THIS PROGRAME IS MY FAVORITE”。 字符 A B C D E F G H I J K L M 频度 186 64 13 22 32 103 21 15 47 57 1 5 32 20 字符 N O P Q R S T U V W X Y Z 频度 57 63 15 1 48 51 80 23 8 18 1 16 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值