利用哈夫曼編碼對文件進行壓縮解壓之貪心算法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 數據輸入文件格式

在這裏插入圖片描述
在這裏插入圖片描述
一行字符串

3 運行結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章