zoukankan      html  css  js  c++  java
  • 20172303 2018-2019-1《程序设计与数据结构》哈夫曼树编码与解码

    20172303 2018-2019-1《程序设计与数据结构》哈夫曼树编码与解码

    哈夫曼树简介

    • 定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
    • 带权路径长度(Weighted Path Length of Tree,简记为WPL)
      • 结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。
      • 结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
      • 树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和。

    哈夫曼树的构造

    • 哈夫曼树并不唯一,但带权路径长度一定是相同的。下面用一个例子来解释哈夫曼树的构造。
    • (1)假设有8个结点,其权值大小如下
    • (2)从中选择最小的两个结点2和3,合成一颗树,根结点的值为两个孩子结点的值相加
    • (3)从剩余的结点(包括新生成的树的根结点)中在选择两个最小的结点5和6,构造新树
    • (4)继续从中进行选择,此时选择7和10,因为这两个结点都和之前生成的树无关,所以他们重新生成一棵树(如果两个数的和正好是下一步的两个最小数的其中的一个,那么这个树直接往上生长就可以了,如果这两个数的和比较大,不是下一步的两个最小数的其中一个,那么就并列生长)
    • (5)从19,21,32,11,17中进行选择,选择11和17,构造新树
    • (6)选择19和21,构造新树
    • (7)选择28和32,构造新树
    • (8)此时只剩下两颗树,将其重新构造一颗新树,一颗哈夫曼树就成型了

    哈夫曼树代码实现

    HuffmanNode类

    • 首先设置一个HuffmanNode类作为实现的基础,每个结点都包含一个六项内容:权值、结点代表字母、字母的编码、左孩子、右孩子和父结点,为了方便之后进行结点的比较,这里还重新编写了一下compareTo方法。
    public int compareTo(HuffmanNode<T> o) {
        if (this.getWeight() > o.getWeight()){
            return -1;
        }
        else if (this.getWeight() < o.getWeight()){
            return 1;
        }
        return 0;
    }
    

    HuffmanTree类

    • HuffmanTree类里有两个方法,第一个方法createTree方法用于构造树,第二个方法BFS方法是使用广度优先遍历来给每一个叶子结点进行编码。具体方法及步骤在代码中都已写明。
    public static HuffmanNode createTree(List<HuffmanNode<String>> nodes) {
        while (nodes.size() > 1){
            // 对数组进行排序
            Collections.sort(nodes);
            // 当列表中还有两个以上结点时,构造树
            // 获取权值最小的两个结点
            HuffmanNode left = nodes.get(nodes.size() - 2);
            left.setCode(0 + "");
            HuffmanNode right = nodes.get(nodes.size() - 1);
            right.setCode(1 + "");
            // 生成新的结点,新结点的权值为两个子节点的权值之和
            HuffmanNode parent = new HuffmanNode(left.getWeight() + right.getWeight(), null);
            // 使新结点成为父结点
            parent.setLeft(left);
            parent.setRight(right);
            // 删除权值最小的两个结点
            nodes.remove(left);
            nodes.remove(right);
            nodes.add(parent);
        }
        return nodes.get(0);
    }
    
    public static List<HuffmanNode> BFS(HuffmanNode root){
        Queue<HuffmanNode> queue = new ArrayDeque<HuffmanNode>();
        List<HuffmanNode> list = new java.util.ArrayList<HuffmanNode>();
    
        if (root != null){
            // 将根元素加入队列
            queue.offer(root);
            root.getLeft().setCode(root.getCode() + "0");
            root.getRight().setCode(root.getCode() + "1");
        }
    
        while (!queue.isEmpty()){
            // 将队列的队尾元素加入列表中
            list.add(queue.peek());
            HuffmanNode node = queue.poll();
            // 如果左子树不为空,将它加入队列并编码
            if (node.getLeft() != null){
                queue.offer(node.getLeft());
                node.getLeft().setCode(node.getCode() + "0");
            }
            // 如果右子树不为空,将它加入队列并编码
            if (node.getRight() != null){
                queue.offer(node.getRight());
                node.getRight().setCode(node.getCode() + "1");
            }
        }
        return list;
    }
    

    HuffmanMakeCode类

    • HuffmanMakeCode类用于将文件中的内容提取,放入数组并进行计数,这里将数组长度设置为27,因为还对空格进行了计数,以便于解码。具体方法及步骤在代码中都已写明。
    public class HuffmanMakeCode {
        public static char[] word = new char[]{'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s'
                ,'t','u','v','w','x','y','z',' '};
        public static int[] number = new int[]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
    
        public static String makecode(FileInputStream stream) throws IOException {
            //读取文件(缓存字节流)
            BufferedInputStream in = new BufferedInputStream(stream);
            //一次性取多少字节
            byte[] bytes = new byte[2048];
            //接受读取的内容(n就代表的相关数据,只不过是数字的形式)
            int n = -1;
            String a = null;
            //循环取出数据
            while ((n = in.read(bytes, 0, bytes.length)) != -1) {
                //转换成字符串
                a = new String(bytes, 0, n, "GBK");
            }
    
            // 对文件内容进行计数
            count(a);
    
            return a;
        }
    
        // 实现对文件内容计数,内层循环依次比较字符串中的每个字符与对应字符是否相同,相同时计数;外层循环指定对应字符从a至空格
        public static void count(String str){
            for (int i = 0;i < 27;i++){
                int num = 0;
                for (int j = 0;j < str.length();j++){
                    if (str.charAt(j) == word[i]){
                        num++;
                    }
                }
                number[i] += num;
            }
        }
    
        public static char[] getWord() {
            return word;
        }
    
        public static int[] getNumber() {
            return number;
        }
    }
    

    HuffmanTest类

    • HuffmanTest类进行了文件的读取,构造哈夫曼树,编码,解码,文件的写入五个步骤,其中前三个步骤使用之前三个类中的方法即可实现,这里主要说一下后两个步骤。
    • 解码:解码部分使用一个列表list4将编码结果的字符串转化到列表中去,然后定义了两个变量,第一个变量用于每次依次获取的编码值,然后与list3(存储编码的列表)进行比较找到对应索引,然后将list2(存储字母的列表)中对应索引值位置的字母加入第二个变量中,每次循环后删除列表list4的第一个元素,循环直至list4为空时结束,第二个变量temp1中存储的即为解码结果。
    // 进行解码
    List<String> list4 = new ArrayList<>();
    for (int i = 0;i < result.length();i++){
        list4.add(result.charAt(i) + "");
    }
    String temp = "";
    String temp1 = "";
    while (list4.size() > 0){
        temp += "" + list4.get(0);
        list4.remove(0);
        for (int i = 0;i < list3.size();i++){
            if (temp.equals(list3.get(i))){
                temp1 += "" + list2.get(i);
                temp = "";
            }
        }
    }
    System.out.println("文件解码结果为: " + temp1);
    
    • 文件写入:文件写入就是很简单的方法使用,这里使用的是字符操作流(使用FileWriter类和FileReader类)的方法。
    // 写入文件
    File file = new File("C:\Users\45366\IdeaProjects\fwq20172303_Programming\HuffmanTest2.txt");
    Writer out = new FileWriter(file);
    out.write(result);
    out.close();
    

    实验结果

    • 所读取的文件
    • HuffmanTest类测试结果

    • 编码写入文件

    参考资料

  • 相关阅读:
    现在连Linux都搞不懂,当初我要是这么学习操作系统就好了!
    一时技痒,撸了个动态线程池,源码放Github了
    Java线程池ThreadPoolExecutor使用和分析(一)
    canch----1.对缓存的思考
    1.java 内存数据库--H2数据库使用指南
    What’s the difference between persist, save, merge and update? Which one should you use?
    primary key's generator in JPA entity
    STM32F103驱动M24256 256k存储芯片进行读写
    【Proteus+51单片机学习笔记】-51/52系列单片机简介
    【STM32项目笔记】STM32CubeMX+Keil+Proteus联合实现LED闪烁
  • 原文地址:https://www.cnblogs.com/PFrame/p/10111647.html
Copyright © 2011-2022 走看看