java_实现Haffman树及其编码与解码
哈夫曼树的创建与哈夫曼编码的实现
目的和要求:
(1)正确定义哈夫曼树结点
(2)掌握哈夫曼树的创建方法
(3)掌握根据哈夫曼树进行编码的方法
(4)根据哈夫曼编码解决实际问题
实验原理及内容:
(1)定义哈夫曼树结点
(2)哈夫曼树的创建方法
(3)根据哈夫曼树进行编码
实验步骤:
(1)定义哈夫曼树结点
(2)哈夫曼树的创建方法
(3)根据哈夫曼树进行编码
实验过程:
(1)哈夫曼树结点定义
public class HNode { private int weight; //结点权值 private int lchild; //左孩子结点 private int rchild; //右孩子结点 private int parent; //父结点 private String name; //结点数据,存放字符名称 private String code;//存放叶子结点的字符编码 //构造器 public HNode(String name, int w){ this.weight = w; this.name = name; this.lchild = -1; this.rchild = -1; this.parent = -1; this.code = ""; } public HNode(){ this(null,0); } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public int getLchild() { return lchild; } public void setLchild(int lchild) { this.lchild = lchild; } public int getRchild() { return rchild; } public void setRchild(int rchild) { this.rchild = rchild; } public int getParent() { return parent; } public void setParent(int parent) { this.parent = parent; } public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(2)哈夫曼树的定义、创建及哈夫曼编码、解码
import java.util.LinkedList; import java.util.Queue; import java.util.Scanner; public class HuffmanTree { private HNode[] data; // 结点数组 private int leafNum; // 叶子结点数目 // 构造哈夫曼树 public void create() { Scanner sc = new Scanner(System.in); System.out.println("请输入要传输的报文:"); String str = sc.nextLine().toLowerCase(); str = str.replace(" ", ""); //去掉空格 int[] c = new int[26]; for (int i = 0; i < str.length(); i++) { // 统计各字符出现的频率 c[str.charAt(i) - 'a']++; } int cnt = 0; for (int i = 0; i < 26; i++) { // 统计报文中字符的数量 if (c[i] > 0) cnt++; } this.leafNum = cnt; data = new HNode[this.leafNum * 2 - 1]; for (int i = 0; i < 2 * leafNum - 1; i++) data[i] = new HNode(); cnt = 0; for (int i = 0; i < 26; i++) { // 用字符创建叶子结点 if (c[i] > 0) { data[cnt].setName((char) (i + 'a') + ""); data[cnt++].setWeight(c[i]); } } int m1, m2, x1, x2; // 处理n个叶子结点,建立哈夫曼树 for (int i = 0; i < this.leafNum - 1; ++i) { m1 = m2 = Integer.MAX_VALUE; // m1:最小权值,m2:次小权值 x1 = x2 = 0; // x1:权值最小位置,x2:权值次小位置 // 在全部结点中找权值最小的两个结点 for (int j = 0; j < this.leafNum + i; ++j) { if ((data[j].getWeight() < m1) && (data[j].getParent() == -1)) { m2 = m1; x2 = x1; m1 = data[j].getWeight(); x1 = j; } else if ((data[j].getWeight() < m2) && (data[j].getParent() == -1)) { m2 = data[j].getWeight(); x2 = j; } } // 用两个权值最小点构造一个新的中间结点 data[this.leafNum + i].setWeight(data[x1].getWeight() + data[x2].getWeight()); data[this.leafNum + i].setLchild(x1); data[this.leafNum + i].setRchild(x2); // 修改权值最小的两个结点的父结点指向 data[x1].setParent(this.leafNum + i); data[x2].setParent(this.leafNum + i); } } //输出哈夫曼树结构 public void print() { System.out.println("位置\t字符\t权值\t父结点\t左孩子结点\t右孩子结点"); for (int i = 0; i < 2 * leafNum - 1; i++) { System.out.printf("%d\t%s\t%d\t%d\t%d\t%d\r\n", i, data[i].getName(), data[i].getWeight(), data[i].getParent(), data[i].getLchild(), data[i].getRchild()); } } // 前序遍历,输出所有叶子结点的编码,并计算总的报文编码长度 private int preorder(HNode root,String code) { int sum = 0; if (root != null) { root.setCode(code); if(isLeaf(root)){ //叶子结点,输出编码,并计算长度 System.out.println(root.getName() + ":" + root.getCode()); return root.getWeight()*root.getCode().length(); } if(root.getLchild()!=-1){ //左子树,编码为0,并统计左子树叶子结点的编码长度 sum +=preorder(data[root.getLchild()],code+"0"); } if(root.getRchild()!=-1){ //右子树,编码为1,并统计右子树所有叶子结点的编码长度 sum +=preorder(data[root.getRchild()],code+"1"); } } return sum; } //层序遍历,求报文传输的总长度 private void levelOrder(){ //根结点的位置 int root = 2*leafNum-2; // 根结点为空 if (root == -1) { return; } // 设置一个队列保存层序遍历的结点 Queue<HNode> q = new LinkedList<HNode>(); // 根结点入队 q.add(data[root]); int sum = 0; String code = ""; // 队列非空,结点没有处理完 while (!q.isEmpty()) { // 结点出队 HNode tmp = q.poll(); code = tmp.getCode(); // 如果是叶子结点,则计算编码长度 if(isLeaf(tmp)){ sum +=tmp.getWeight()*tmp.getCode().length(); } // 将当前结点的左孩子结点入队 if (tmp.getLchild() != -1) { q.add(data[tmp.getLchild()]); data[tmp.getLchild()].setCode(code+"0"); } if (tmp.getRchild() != -1) { // 将当前结点的右孩子结点入队 q.add(data[tmp.getRchild()]); data[tmp.getRchild()].setCode(code+"1"); } } System.out.println("总的报文长度为:"+sum); } //采用层序遍历,进行报文解码 public String decodes(String codes){ //根结点的位置 int root = 2*leafNum-2; // 根结点为空 if (root == -1) { return ""; } // 设置一个队列保存层序遍历的结点 Queue<HNode> q = new LinkedList<HNode>(); // 根结点入队 q.add(data[root]); int i = 0; String str = ""; // 队列非空,结点没有处理完 while (!q.isEmpty()) { // 结点出队 HNode tmp = q.poll(); if(!codes.startsWith(tmp.getCode())) continue; // 如果是叶子结点,则计算编码长度 if(isLeaf(tmp)){ str = str + tmp.getName(); codes = codes.substring(tmp.getCode().length()); if(codes.length()>0){ //如果存在多个报文字符,则继续重新解码 while(!q.isEmpty()) q.poll(); q.add(data[root]); continue; } } // 将当前结点的左孩子结点入队 if (tmp.getLchild() != -1) { q.add(data[tmp.getLchild()]); } if (tmp.getRchild() != -1) { // 将当前结点的右孩子结点入队 q.add(data[tmp.getRchild()]); } } return str; } // 层次遍历 public void traverse() { //根结点的位置 int root = 2*leafNum-2; // 根结点为空 if (root == -1) { return; } int sum = preorder(data[root],""); System.out.println("所有报文长度为(位):"+sum); } // 判断是否是叶子结点 public boolean isLeaf(HNode p) { return ((p != null) && (p.getLchild() == -1) && (p.getRchild() == -1)); } }
(3)哈夫曼树测试
import java.util.Scanner; public class TestHuffmanTree { // 测试哈夫曼树 public static void main(String[] args) { HuffmanTree ht = new HuffmanTree(); ht.create(); //创建哈夫曼树 //ht.print(); //输出哈夫曼树结构 ht.traverse(); //输出所有字符编码 String op = ""; do{ System.out.println("请输入一个报文编码进行解码:"); Scanner sc = new Scanner(System.in); String codes = sc.nextLine(); String decodes = ht.decodes(codes); //报文解码 if(decodes.length()==0){ System.out.println("解码出错!"); }else{ System.out.println("对应的报文为:"+decodes); } System.out.println("按X键退出,其他键继续"); op = sc.nextLine(); }while(!op.toLowerCase().equals("x")); System.out.println("程序退出"); } }