利用哈夫曼编码对文件进行压缩解压之贪心算法java实现
1 算法实现
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;
//哈夫曼树类
class HaffmanTree {
public static final int MAXVALUE = 1000;// 最大权值
public int nodeNum; // 叶子结点个数
public HaffmanTree(int n) {
this.nodeNum = n;
}
/**
* 构造哈夫曼树算法
*
* @param weight 权值
* @param nodes 叶子节点
*/
public void haffman(char[] names, int[] weight, HaffNode[] nodes) {
int n = this.nodeNum;
// m1,m2,表示最小的两个权值,x1,x2,表示最小两个权值对应的编号,m1表示最小,m2表示次小
int m1, m2, x1, x2;
// 初始化所有的结点,对应有n个叶子结点的哈夫曼树,有2n-1个结点
for (int i = 0; i < 2 * n - 1; i++) {
HaffNode temp = new HaffNode();
// 初始化n个叶子结点,就是输入的节点。0、1、2、3是叶子节点也是输入的节点
if (i < n) {
temp.name = names[i];
temp.weight = weight[i];
} else {
temp.name = ' ';
temp.weight = 0;
}
temp.parent = 0;
temp.flag = 0;
temp.leftChild = -1;
temp.rightChild = -1;
nodes[i] = temp;
}
// 初始化n-1个非叶子结点,n-1表示要循环n-1次求的n-1个数
for (int i = 0; i < n - 1; i++) {
m1 = m2 = MAXVALUE;
x1 = x2 = 0;
// 求得这n-1个数时,每次都是从0到n+i-1,并且flag=0的,flag=1表示已经加入到二叉树。
// 以下2步是找出权值最小的2个
for (int j = 0; j < n + i; j++) {
if (nodes[j].weight < m1 && nodes[j].flag == 0) {
// m1,x1初始值为第一个元素,后面如果比m1要小,则m1指向更小的,原来m1指向的现在由m2指向,
// 如果后面比m1大比m2小,则m2指向这个比m1大比m2小的,
// 也就是说m1指向最小的,m2指向第2小的。
m2 = m1;
x2 = x1;
m1 = nodes[j].weight;
x1 = j;
} else if (nodes[j].weight < m2 && nodes[j].flag == 0) {
m2 = nodes[j].weight;
x2 = j;
}
}
// 将权值最小的2个组合成一个2插树
nodes[x1].parent = n + i;
nodes[x2].parent = n + i;
nodes[x1].flag = 1;
nodes[x2].flag = 1;
nodes[n + i].weight = nodes[x1].weight + nodes[x2].weight;
nodes[n + i].leftChild = x1;
nodes[n + i].rightChild = x2;
}
}
/**
* 哈弗曼编码算法
* @param nodes
* @param haffCode
*/
public void haffmanCode(HaffNode[] nodes, Code[] haffCode) {
int n = this.nodeNum;
Code code = new Code(n);
int child, parent;
// 给前面n个输入的节点进行编码
for (int i = 0; i < n; i++) {
code.start = n - 1;
code.weight = nodes[i].weight;
code.name = nodes[i].name;
child = i;
parent = nodes[child].parent;
// 从叶子节点向上走来生成编码。
while (parent != 0) {
if (nodes[parent].leftChild == child) {
code.bit[code.start] = 0;
} else {
code.bit[code.start] = 1;
}
code.start--;
child = parent;
parent = nodes[child].parent;
}
Code temp = new Code(n);
for (int j = code.start + 1; j < n; j++) {
temp.bit[j] = code.bit[j];
}
temp.weight = code.weight;
temp.name = code.name;
temp.start = code.start;
haffCode[i] = temp;
}
}
public void jiema(String res, HaffNode[] nodes){
// 从根节点出发
int index = 2*this.nodeNum-2;
for(int k = 0; k < res.length(); k++){
// 依次读取哈夫曼编码
// 遇0则遍历当前节点的左孩子
if(res.charAt(k)=='1'){
// 遇1则遍历当前节点的右孩子
index = nodes[index].rightChild;
// 如果当前节点的右孩子为-1是证明其为叶子节点直接输出字符(由于哈夫曼树只存在出度为0或2的节点,因此只判断右孩子即可)
if(nodes[index].rightChild==-1){
System.out.print(nodes[index].name);
// 重新从根节点出发
index = 2*this.nodeNum-2;
}
}else{
// 遇1则遍历当前节点的右孩子
index = nodes[index].leftChild;
// 如果当前节点的右孩子为-1是证明其为叶子节点直接输出字符(由于哈夫曼树只存在出度为0或2的节点,因此只判断右孩子即可)
if(nodes[index].rightChild==-1){
System.out.print(nodes[index].name);
// 重新从根节点出发
index = 2*this.nodeNum-2;
}
}
}
}
}
// 哈夫曼树的结点类
class HaffNode {
public char name; // 字符名
public int weight; // 权值
public int parent; // 他的双亲
public int flag; // 标志,是否为叶子节点
public int leftChild; // 他的左孩子
public int rightChild; // 他的右孩子
public HaffNode() {}
}
// 哈夫曼编码类
class Code {
public int[] bit; // 编码的数组
public int start; // 编码的开始下标
public int weight; // 权值
public char name; // 字符名
public Code(int n) {
bit = new int[n];
start = n - 1;
}
}
public class Demo {
public char[] names;
public int[] weights;
public static void main(String[] args) throws Exception {
Demo test = new Demo();
while(true){
// 读取文本中的一行字符串
String s = test.readfile();
// 统计文本中不同字符出现的频率
Map<Character, Integer> map = test.getCharMaps(s);
// 创建数组 用于储存字符及出现频率
test.names = new char[map.size()];
test.weights = new int[map.size()];
int i = 0;
// 将map转化为set 并将统计的字符及其对应的频次放入到数组中
Set set = map.keySet();
for (Iterator iter = set.iterator(); iter.hasNext();) {
char key = (char) iter.next();
test.names[i] = key;
test.weights[i] = map.get(key);
i++;
}
System.out.println("****************文本中的不同字符及其对应出现的频率******************");
// 打印字符
for (int j = 0; j < test.names.length; j++) {
System.out.print(test.names[j] + " ");
}
System.out.println();
// 打印频次
for (int j = 0; j < test.weights.length; j++) {
System.out.print(test.weights[j] + " ");
}
System.out.println();
// 建立哈夫曼树
HaffmanTree haffTree = new HaffmanTree(map.size());
HaffNode[] nodes = new HaffNode[2 * map.size() - 1];
Code[] codes = new Code[map.size()];
// 构造哈夫曼树
haffTree.haffman(test.names, test.weights, nodes);
// 生成哈夫曼编码
haffTree.haffmanCode(nodes, codes);
// 打印哈夫曼编码
System.out.println("************************哈夫曼编码表**************************");
for (int k = 0; k < map.size(); k++) {
System.out.print("Name=" + codes[k].name + " Weight=" + codes[k].weight + " Code=");
for (int j = codes[k].start + 1; j < map.size(); j++) {
System.out.print(codes[k].bit[j]);
}
System.out.println();
}
System.out.println("**************************原始数据***************************");
System.out.println(s);
System.out.println("**********************哈夫曼编码后的数据************************");
String res = s;
String bit = "";
// 根据哈夫曼编码表替换相应字符
for(int k = 0; k < test.names.length; k++){
for (int j = codes[k].start + 1; j < map.size(); j++) {
bit += codes[k].bit[j];
}
res = res.replace(String.valueOf(test.names[k]), bit);
bit = "";
}
System.out.println(res);
System.out.println("**************************译码后的数据************************");
haffTree.jiema(res, nodes);
System.out.println();
}
}
/**
* 读取文本文件
*
* @return 文本中一行字符串
* @throws Exception
*/
public String readfile() throws Exception {
/*
* 选择测试文件序号
*/
System.out.println("共六组测试数据(1~6),请输入数据编号:");
Scanner sc = new Scanner(System.in);
int num = sc.nextInt();
/*
* 读取数据
*/
BufferedReader br = new BufferedReader(new FileReader("./src/input_assgin03_0" + num + ".dat"));
return (br.readLine());
}
/**
* 统计文本中不同字符对应的出现频率
*
* @param s 待检测的字符串
* @return 不同字符 对应其出现的频率的 map
*/
public Map<Character, Integer> getCharMaps(String s) {
Map<Character, Integer> map = new HashMap<Character, Integer>();
for (int i = 0; i < s.length(); i++) {
Character c = s.charAt(i);
Integer count = map.get(c);
map.put(c, count == null ? 1 : count + 1);
}
return map;
}
}
2 数据输入文件格式
一行字符串