基于哈夫曼树对txt文件的解压与压缩

目录

一、什么是哈夫曼树

二、构造哈夫曼树

1.新建Node类

 2.创建结点

3.对结点进行从小到大排序

 4.构造树

三、哈夫曼编码

四、压缩

1.读取文本信息

2.用HashMap统计每个字符出现的频率,创建对应的节点

3.把字符串数据替换成对应的编码

4.把字符编码每8个一组转成byte写入文件

五、解压

1.读文件数据并转成二进制

2.将编码转成字符串

3.将字符串写入到文件中

一、什么是哈夫曼树


  我们知道,从树根到某个结点的路径是从根结点开始沿着某个分支到达该结点的一个结点序列,路径所含的分支数(结点个数减1)称为此路径的长度。而一棵树的路径长度是指从树根到其余各结点的路径长度之和。结点的带权路径长度是指从根结点到该结点之间的路径长度与该结点上所带权值的乘积。设一棵树有 n个叶结点,每个叶结点带有权值Wₖ,从根结点到每个叶结点的长度lₖ,则每个叶结点的带权路径长度之和就是这棵树的带权路径长度(WPL),它可以被表

WPL=\sum_{k=1}^{n}W_{k}I_{k}

哈夫曼树就是WPL值最小的二叉树,它的特点是权值越大的叶结点越靠近根结点,而权值越小的叶结点越远离根结点。


例如队列:3,7,5,1,8 。对这一组数字进行从小到大的规则排序,排序后为1,3,5,7,8。在这些数字中选择两个最小的数字:1,3 用类似树杈的“树枝”连接两个最小的数,在顶点处计算出这两个数字的和4,将这两数字和加入队列中,并比较剩下的数字和这个和的大小,再取出两个最小的数字进行排序。重复上述操作,构成的哈夫曼树如右图所示

二、构造哈夫曼树

1.新建Node类

class TreeNode{
	int data;                      //结点权值
	public TreeNode left;          //指向左子树
	public TreeNode right;         //指向右子树
	
	public TreeNode(int data) {    //构造方法,创建结点是传入权值
		this.data = data;
	}
}

 2.创建结点

int[] arr = {7,5,9,6,3,8,10,4};                 //数组存放结点权值
List<TreeNode> list = new ArrayList<>();        //队列,存放结点
for(int i = 0;i < arr.length;i++) {             
    TreeNode node = new TreeNode(m.charAt(i));  //创建结点
	list.add(node);                             //将结点存入队列list中
}

3.对结点进行从小到大排序

 使用二分法,根据结点的权值对结点从小到大排序。

public void sort(List<TreeNode> list,int start,int end) {
		int left = start;
		int right = end;
		TreeNode key1 =  list.get(start);
		int key = key1.data;
		while(right > left) {
			while(list.get(right).data>=key && right > left) {
				right--;
			}
			while(list.get(left).data<=key && right > left) {
				left++;
			}
			if(list.get(left).data > list.get(right).data) {
				TreeNode temp = list.get(left);
				list.set(left, list.get(right));
				list.set(right, temp);
			}
		}
		list.set(start, list.get(left));
		list.set(left, key1);
		if(start < left) {
			sort(list,start,left-1);
		}
		if(right < end) {
			sort(list,left+1,end);
		}
	}

 4.构造树

public TreeNode creatTree() {
	TreeNode leftNode;
	TreeNode rightNode;
	TreeNode node;
	while(list.size() > 1) {                //当队列中没有元素时结束循环
		leftNode = list.remove(0);          //取出队列的第一个结点
		rightNode = list.remove(0);         //取出的是权值最小的两个结点
		node = new TreeNode(leftNode.data + rightNode.data);	//创建权值为最小两个数和的结点
		node.left = leftNode;               
		node.right = rightNode;
		list.add(node);                     //将新结点存入队列中
		sort(list,0, list.size()-1);        //对结点从小到大排序
		if(list.size()==1) {                //当队列中只有一个结点时,该结点就是哈夫曼树的根结点
			node = list.remove(0);
			node.left = leftNode;
			node.right = rightNode;
			return node;
		}
	}
	return null;
}

三、哈夫曼编码

每个结点的左分支被记为0,右分支被记为1。某一字符的编码可通过组合从根结点到该字符结点(叶结点)的路径上所标注的0、1得到。注意前缀编码树的特点是每个字符必是叶结点,且树中没有度为1的结点。一个字符的编码长度即是该字符结点在其哈夫曼树中的深度d-1。哈夫曼编码的特点使得出现频率高的字符编码短些,出现频率低的字符编码长些,这样可以达到更好的压缩效果。

在前面新建的Node类中,加入code的元素。

//用递归方法给树的叶子编码
public void setCode(TreeNode node) {
	if(node.left != null) {
		node.left.code = node.code + "0"; 
		setCode(node.left);
	}
	if(node.right != null) {
		node.right.code = node.code + "1";
		setCode(node.right);
	}
}

四、压缩

1.根据给定的字符串内容(读取文本文件),统计每个字符出现的频率,创建对应的节点
2.根据节点的权值创建哈夫曼树,统计字符编码
3.把字符数据替换成对应的编码
4.把字符编码每8个一组转成byte写入文件:最后一个字节不足要补0,将它补满8位
5.把码表写入文件

1.读取文本信息

public String readFile(String path) {
	File file = new File(path);
	FileReader fr;
	String str = "";       //存放文本信息
	try {
		fr = new FileReader(file);
		BufferedReader br = new BufferedReader(fr);        //可以对文件进行行输入
		String s = "";
		while((s = br.readLine()) != null) {               //br.readLine()行输入
			str += s;
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
	return str;
}

2.用HashMap统计每个字符出现的频率,创建对应的节点

public void number(String m) {                    //s为文本信息
	hm = new HashMap<>();
	for(int i = 0;i < m.length();i++) {
		if(!(hm.containsKey(m.charAt(i)+""))) {   //与hm比较,若没有该字符
			hm.put(m.charAt(i)+"",1);             //m.charAt(i)是char类型,+""自动转为String
		}else {
			hm.put(m.charAt(i)+"", hm.get(m.charAt(i)+"")+1);//若有该字符,则+1;
		}
	}
	for(String s : hm.keySet()) {                 
		System.out.print(s + ":" + hm.get(s) + " ");
		StrTreeNode node = new StrTreeNode(hm.get(s),s);  //创建结点
		list.add(node);
	}
}

接下来就是用得到的结点,根据结点权值,也就是字符出现的频率进行从小到大排序,然后再构造树并生成哈夫曼编码,并将哈夫曼编码存入码表hmcode

3.把字符串数据替换成对应的编码

//将哈夫曼编码存入码表
HashMap<String,String> hmcode = new HashMap<>();  //存放字符和对应的编码,码表
public void inorder(StrTreeNode node,String m) {
	if(node.left == null && node.right == null) {
		hmcode.put(node.s, node.code);          //存入码表
	}
	if(node.left != null) {
		inorder(node.left,m);
	}
	if(node.right != null) {
		inorder(node.right,m);
	}
}
//把字符串数据替换成对应的编码
public String setCode(String m) {
	StringBuilder stringb = new StringBuilder();
	for(int i = 0;i < m.length();i++) {
		stringb.append(hmcode.get(m.charAt(i)+""));		
	}
	return stringb.toString();
}

4.把字符编码每8个一组转成byte写入文件

//把字符编码没每8个一组转成byte写入文件:最后一个字节不足要补全
public void change(String n) throws Exception {
	FileOutputStream fos = new FileOutputStream(fName);
	ObjectOutputStream oos = new ObjectOutputStream(fos);
	int c = 8 - n.length() %8;   
	int x = 8 - c;
	int data;
	if(c!=0) {           //不足8为补0
		for(int i = 1;i <= x;i++) {
			n += '0';
		}
	}
	for(int i = 0;i < n.length();i=i+8) {
		String a;
		String b = "";
		b += n.charAt(i);
		b += n.charAt(i+1);
		b += n.charAt(i+2);
		b += n.charAt(i+3);
		b += n.charAt(i+4);
		b += n.charAt(i+5);
		b += n.charAt(i+6);
		b += n.charAt(i+7);
		data = bit_to_byte(b);
		oos.write(data);       //将数值写入文件
	}
	oos.writeObject(hmcode);   //将码表存入文件
	oos.close();
}
//8位二进制转成byte即十进制
public int bit_to_byte(String bit2) {
	int y = 2;
	int x0 = bit2.charAt(7) - '0';
	int x1 = (bit2.charAt(6) - '0')*y;
	y = y*2;
	int x2 = (bit2.charAt(5) - '0')*y;
	y = y*2;
	int x3 = (bit2.charAt(4) - '0')*y;
	y = y*2;
	int x4 = (bit2.charAt(3) - '0')*y;
	y = y*2;
	int x5 = (bit2.charAt(2) - '0')*y;
	y = y*2;
	int x6 = (bit2.charAt(1) - '0')*y;
	y = y*2;
	int x7 = (bit2.charAt(0) - '0')*y;
	y = y*2;
	return (x0+x1+x2+x3+x4+x5+x6+x7);
}

五、解压

解压部分相对于压缩部分要简单很多

1.读取文件数据,把byte数据转成二进制

2.二进制不足8为要补0

 3.根据码表把二进制数据还原成对应的字符内容,再写入文件

1.读文件数据并转成二进制

//读需要解压的文件,并转成编码的形式
public String read() {
	String code = "";
	ObjectInputStream ois;
	try {
		FileInputStream fips = new FileInputStream(fName);
		ois = new ObjectInputStream(fips);
		int n = ois.read();
		while(n !=-1) {           //当文件数据读到最后,ois.read()返回-1
			code += this.byte_to_bit(n);   //十进制转二进制
			n = ois.read();
		}
		hm = (HashMap)ois.readObject();
	} catch (Exception e) {}
	return code;
}
//十进制转二进制,并补0
public String byte_to_bit(int data) {
	int m = data;
	int count = 0;
	String n = "";
	while(m!=0) {
		int a = m%2;
		m = m/2;
		count++;
		n += String.valueOf(a);  //int类型转String
	}
	for(int i = count+1;i <=8;i++) {  //补0
		n += "0";
	}
	String next = "";
	for(int i = 7;i >= 0;i--) {      //由于读从文件读出的数据是倒置的,使用将其倒置得到原来的编码
		next += n.charAt(i);
	}
	return next;
}

2.将编码转成字符串

public String imcode(String code) {
	String m = "";
	String n = "";
	for(int i = 0;i < code.length();i++) {
		n += code.charAt(i);
		Iterator<String> iterator = hm.keySet().iterator(); //遍历码表
        while (iterator.hasNext()) {
            Object key = iterator.next();
            if(n.equals(hm.get(key))) {     
            	m += key;
            	n = "";	
            }
        }
	}
	return m;
}

3.将字符串写入到文件中

public void intofile(String m) {
	File file = new File(fName);
	try {
		FileOutputStream fos = new FileOutputStream(file);
		PrintStream ps = new PrintStream(fos);
		ps.print(m);
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}
}

六、添加图形化界面

public void show() {
	JFrame jf = new JFrame();
	jf.setSize(240, 120);
	jf.setTitle("压缩");
	jf.setLocationRelativeTo(null);
	jf.setDefaultCloseOperation(3);
	FlowLayout flow = new FlowLayout();
	jf.setLayout(flow);
	//按钮和文本框
	JButton jb1 = new JButton("压缩");
	jf.add(jb1);
	JButton jb2 = new JButton("解压");
	jf.add(jb2);
	JLabel jl = new JLabel("需要进行操作的文件是:");
	jf.add(jl);
	JTextField jtf = new JTextField();
	Dimension dm = new Dimension(200,20);
	jtf.setPreferredSize(dm);
	jf.add(jtf);
	//监听器
	ButtonMouse mouse = new ButtonMouse(jf,jtf);
	jb1.addMouseListener(mouse);
	jb1.addActionListener(mouse);
	jb2.addMouseListener(mouse);
	jb2.addActionListener(mouse);
	
	jf.setVisible(true);
}

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
综合实验: 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、付费专栏及课程。

余额充值