package org.rjb.util; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Scanner; /** * 通过输入元素的权值计算出该元素的赫夫曼编码 * @author LovinChan * @version 1.2 * * 要不是一个我也不知道什么名字的实验课,我也没想到自己动手写这么个丑程序,开始写的 * 时候还真的遇到了一些困难,主要是对赫夫曼树的数据结构不熟悉,所以刚开始写这个程序的 * 时候还真的动了一番脑筋,也在网上下了一些代码看了看,可是用我自己的数据测试的时候发 * 现那些程序很多还是有问题的,于是乎悲剧性的我决定自己写这么个程序,通过输入元素的权 * 值计算该元素的赫夫曼编码。 * 权值暂时只能用整数,因为写得比较赶,可能很多地方都没考虑到,希望大家如果发现了错 * 误能给小弟指正出来,3KS 啦,如果想要跟我交流可以加我的 QQ 或者 MSN: * MSN: [email protected] * QQ: 372133556(请注明为什么加我) * 一个小小的赫夫曼编码Java版算法,希望得到大虾们的点评指导。 * */ public class HuffmanCode { // main method public static void main(String[] args) { System.out.println("*************** 程序开始 ***************"); HuffmanCode test = new HuffmanCode(); // test.test(); 测试方法 test.getNodeValue(); test.caculate(); test.result(); System.out.println("*************** 程序结束 ***************"); } /** * 在命令行获取结点元素值和它的权值 */ public void getNodeValue() { // 通过 Scanner 从命令行获得这些值 Scanner scanner = new Scanner(System.in); boolean flag = true; while (flag) { System.out.println("请输入结点值: (输入 \"-q\" 退出)"); String value = scanner.next(); // 输入 -q ,则结束输入 if (value.equals("-q")) { flag = false; System.out.println(nodes.size() + " 个结点待编码"); scanner.close(); return; } else { System.out.println("请输入结点权值(整数): "); // 权值必须暂必须为整数 int authority = scanner.nextInt(); // 构造结点对象 Node node = new Node(value,authority, "", null, null, null); // 添加进待排序结点链表中 nodes.add(node); } } } /** * 获得输出结果 * 结果有输入结点元素值,权值和它的赫夫曼编码,以及平均码长 */ public void result() { int average = 0; for (Node node : sortedNodes) { System.out.println(node); average = average + (node.authority) + node.huffmanCode.length(); } System.out.println("平均码长: " + average); } /** * 先序遍历已排好的赫夫曼树,并把所有的结点加入到一个链表中 * @param node 根结点 */ private void preOrder(Node node) { if (null != node) { nodes.add(node); // 递归算法 preOrder(node.leftChild); preOrder(node.rightChild); } } /** * 计算排好序的赫夫曼树中叶结点的赫夫曼编码,也就是用户输入的结点元素的赫夫曼编码 * @param nodes 排好序的赫夫曼树 */ private void setHuffmanCode(List<Node> nodes) { for (Node node : nodes) { if (null != node.value) { // 对每个叶结点,往上遍历到根结点,并顺序记录每个结点的 huffmanCode // 最后得到的 huffmanCode 就是该叶结点的赫夫曼编码 Node parent = node.parent; String huffmanCode = node.huffmanCode; while (null != parent) { huffmanCode = huffmanCode + parent.huffmanCode; parent = parent.parent; } node.huffmanCode = huffmanCode; // 将叶结点放到另一个链表中存储起来 sortedNodes.add(node); } } } /** * 将用户输入的元素排列起来,并设计一个赫夫曼树,将根结点存到 unsortedNodes 链表中 * 然后通过先序遍历根结点,将每一个结点都存到该链表中,再提取链表中的叶结点,计算得叶 * 结点的赫夫曼编码,转存叶结点。 */ public void caculate() { // nodes 中只剩下一个结点,也就是根结点咯... if (nodes.size() == 1) { Node node = nodes.get(0); // 清空 nodes 链表,删除 nodes 中最后一个元素(根结点) nodes.remove(0); preOrder(node); setHuffmanCode(nodes); return; } // 先将传入结点链表按权值排序 Collections.sort(nodes, new Node()); // 取出前两个元素作为新结点的左右子结点,然后从链表中移除这两个元素 Node leftChild = nodes.get(0); Node rightChild = nodes.get(1); // 左子树叶子结点总是用户输入的元素之一 if (null == leftChild.value && null != rightChild.value) { Node temp = leftChild; leftChild = rightChild; rightChild = temp; } nodes.remove(0); nodes.remove(0); /************************ 开始计算 ************************/ /** 排序结果计算每个传入结点的霍夫曼编码 **/ int authority = leftChild.authority + rightChild.authority; // 构造父结点,权值为左右子结点的权值和 Node parent = new Node(null, authority, "", leftChild, rightChild, null); // 设置左右子结点的 huffmanCode ,左结点为 "0";右结点为 "1" leftChild.huffmanCode = leftChild.huffmanCode + "0"; rightChild.huffmanCode = rightChild.huffmanCode + "1"; // 将子结点的父结点引用指向新创造的父结点 leftChild.parent = parent; rightChild.parent = parent; nodes.add(parent); caculate(); } private List<Node> nodes = new ArrayList<Node>(); public List<Node> sortedNodes = new ArrayList<Node>(); /** * 结点描述 * @author LovinChan * @version 1.5 */ class Node implements Comparator<Node> { public Node() {} public Node( String value, int authority, String code, Node leftChild, Node rightChild, Node parent) { this.huffmanCode = code; this.authority = authority; this.value = value; this.leftChild = leftChild; this.rightChild = rightChild; this.parent = parent; } // huffmanCode String huffmanCode; // 权值 int authority; // 值 String value; // 左孩子 Node leftChild; // 右孩子 Node rightChild; // 父结点 Node parent; public String toString() { return "元素值: " + value + "\n权值: " + authority + "\nHuffman code: " + huffmanCode ; } @Override // 便于对 List 排序 public int compare(Node o1, Node o2) { if (o1.authority > o2.authority) { return 1; } else if (o1.authority == o2.authority) { if (null == o1.parent && null == o2.parent) { return 0; } else if (null != o1.parent && null == o2.parent) { return -1; } else if (null == o1.parent && null != o2.parent) { return 1; } else { return 0; } } else { return -1; } } } } /** 测试数据.... * *************** 程序开始 *************** 请输入结点值: (输入 "-q" 退出) a 请输入结点权值(整数): 1 请输入结点值: (输入 "-q" 退出) b 请输入结点权值(整数): 2 请输入结点值: (输入 "-q" 退出) c 请输入结点权值(整数): 3 请输入结点值: (输入 "-q" 退出) d 请输入结点权值(整数): 4 请输入结点值: (输入 "-q" 退出) e 请输入结点权值(整数): 5 请输入结点值: (输入 "-q" 退出) f 请输入结点权值(整数): 6 请输入结点值: (输入 "-q" 退出) g 请输入结点权值(整数): 7 请输入结点值: (输入 "-q" 退出) -q ********** 程序运行结果 **********7 个结点待编码 元素值: f 权值: 6 Huffman code: 00 元素值: c 权值: 3 Huffman code: 010 元素值: a 权值: 1 Huffman code: 0110 元素值: b 权值: 2 Huffman code: 1110 元素值: g 权值: 7 Huffman code: 01 元素值: d 权值: 4 Huffman code: 011 元素值: e 权值: 5 Huffman code: 111 平均码长: 7 *************** 程序结束 *************** */ /* public void test() { Node a = new Node( "a", 5, "", null, null, null); Node b = new Node( "b", 29, "", null, null, null); Node c = new Node( "c", 7, "", null, null, null); Node d = new Node( "d", 8, "", null, null, null); Node e = new Node( "e", 14, "", null, null, null); Node f = new Node( "f", 23, "", null, null, null); Node g = new Node( "g", 3, "", null, null, null); Node h = new Node( "g", 11, "", null, null, null); unsortedNodes.add(a); unsortedNodes.add(b); unsortedNodes.add(c); unsortedNodes.add(d); unsortedNodes.add(e); unsortedNodes.add(f); unsortedNodes.add(g); unsortedNodes.add(h); System.out.println(unsortedNodes.size()); caculate(); int average = 0; for (Node node : sortedNodes) { System.out.println(node); average = average + (node.authority) + node.huffmanCode.length(); } System.out.println("平均码长: " + average / sortedNodes.size()); } */