20172304 蓝墨云实验哈夫曼树

20172304 蓝墨云实验哈夫曼树

实验要求

  • 设有字符集:S={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}。
    给定一个包含26个英文字母的文件,统计每一个字符出现的几率,根据计算的几率构造一颗哈夫曼树。
    并完成对英文文件的编码和解码。
  • 要求:
    • (1) 准备一个包含26个英文字母的英文文件(能够不包含标点符号等),统计各个字符的几率
    • (2) 构造哈夫曼树
    • (3) 对英文文件进行编码,输出一个编码后的文件
    • (4) 对编码文件进行解码,输出一个解码后的文件
    • (5) 撰写博客记录实验的设计和实现过程,并将源代码传到码云
    • (6) 把实验结果截图上传到云班课

哈夫曼编码

  • 哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法彻底依据字符出现几率来构造异字头的平均长度最短的码字,有时称之为最佳编码,通常就叫作Huffman编码(有时也称为霍夫曼编码)。

原理

这个老师上课说过。首先肯定文件中各个字符的几率而后将其。按照几率大小。将几率小的放在离根节点较近的地方。

这里用数字来表明字符的几率而后,首先将几率最小的两个字符放到一块儿,将它们的几率合成父结点的几率,而后父结点参与比较再次在备选集中找到比较小的两两结合,而后再次合成父结点的几率。java

具体实现哈夫曼树

由于哈夫曼须要考虑到父结点的影响,因此定义了相关的左孩子右孩子的方法。node

package HuffmanTree;

public class HNode {
    /**
     * 节点类
     * @author LiRui
     *
     */
       public String code = "";// 节点的哈夫曼编码       public String data = "";// 节点的数据
       public int count;// 节点的权值
       public HNode lChild;
       public HNode rChild;

        public HNode() {
        }

        public HNode(String data, int count) {
            this.data = data;
            this.count = count;
        }

        public HNode(int count, HNode lChild, HNode rChild) {
            this.count = count;
            this.lChild = lChild;
            this.rChild = rChild;
        }

        public HNode(String data, int count, HNode lChild, HNode rChild) {
            this.data = data;
            this.count = count;
            this.lChild = lChild;
            this.rChild = rChild;
        }

    }
  • 构造哈夫曼树
    • 哈夫曼树的构造,并不须要像建立二叉树的那么多的方法,知足造成哈夫曼编码的部分就好。分享的那篇博客有相关的内容,咱们只须要在此基础上添加每次建立的的编码值就能够,左侧0右侧1就能够。
    • 建立树,先将结点进行排序,利用结点类中实现的比较方法,分别将左孩子定义为列表中的倒数第二个,由于左侧编码为0,因此让该结点的编码为0;右孩子为列表中的倒数第一个,由于右侧编码为1,因此让该结点的编码为1,双亲结点根据所讲内容为左右结点权重相加之和,把双亲结点加入列表中,而后删除倒数两个结点并添加双亲结点,再次进行循环,排序,不断从列表中把倒数两个结点删除,直至跳出循环,此时列表中的结点只剩一个,该结点的左右部分包含了全部按照编码进行添加的元素内容。
    public HuffmanNode<T> createTree(List<HuffmanNode<T>> nodes) {
          while (nodes.size() > 1) {
              Collections.sort(nodes);
              HuffmanNode<T> left = nodes.get(nodes.size() - 2);//令其左孩子的编码为0
              left.setCode("0");
              HuffmanNode<T> right = nodes.get(nodes.size() - 1);//令其右孩子的编码为1
              right.setCode("1");
              HuffmanNode<T> parent = new HuffmanNode<T>(null, left.getWeight() + right.getWeight());
              parent.setlChild(left);
              parent.setrChild(right);
              nodes.remove(left);
              nodes.remove(right);
              nodes.add(parent);
          }
          return nodes.get(0);
      }

    这个是构建哈夫曼树的具体流程,由于须要在结点已经链接以后在进行比较因此就将全部出现的字符放在一个链表里便于操做。具体的流程代码有详细介绍。git

    /**
       * 将出现的字符建立成单个的结点对象
       */
      private void creatNodes() {
    
          for (int i = 0; i < charList.size(); i++) {
              String data = charList.get(i).c + "";
              int count = charList.get(i).num;
    
              HNode node = new HNode(data, count); // 建立节点对象
              NodeList.add(node); // 加入到节点链表
          }
    
      }
    
      /**
       * 构建哈夫曼树
       */
      private void creatTree() {
    
          while (NodeList.size() > 1) {// 当节点数目大于一时
              // 4.取出权值最小的两个节点,生成一个新的父节点
              // 5.删除权值最小的两个节点,将父节点存放到列表中
              HNode left = NodeList.poll();
              HNode right = NodeList.poll();
    
              // 在构建哈夫曼树时设置各个结点的哈夫曼编码
              left.code = "0";
              right.code = "1";
              setCode(left);
              setCode(right);
    
              int parentWeight = left.count + right.count;// 父节点权值等于子节点权值之和
              HNode parent = new HNode(parentWeight, left, right);
    
              NodeList.addFirst(parent); // 将父节点置于首位
              Sort(NodeList); // 从新排序,避免新节点权值大于链表首个结点的权值
          }
      }

这个是对结点链表进行排序的类算法

/**
     * 升序排序
     *
     * @param nodelist
     */
    private void Sort(LinkedList<HNode> nodelist) {
        for (int i = 0; i < nodelist.size() - 1; i++) {
            for (int j = i + 1; j < nodelist.size(); j++) {
                HNode temp;
                if (nodelist.get(i).count > nodelist.get(j).count) {
                    temp = nodelist.get(i);
                    nodelist.set(i, nodelist.get(j));
                    nodelist.set(j, temp);
                }

            }
        }

    }

下面介绍一个类这个主要的做用是统计各个字符出现的次数,也就是老师所说的几率,而后再进行排序。测试

private void getCharNum(String str) {

        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i); // 从给定的字符串中取出字符
            flag = true;

            for (int j = 0; j < charList.size(); j++) {
                CharData data = charList.get(j);

                if(ch == data.c){
                    // 字符对象链表中有相同字符则将个数加1
                    data.num++;
                    flag = false;
                    break;
                }
            }

            if(flag){
                // 字符对象链表中没有相同字符则建立新对象加如链表
                charList.add(new CharData(ch));
            }

        }

    }

这是输出每一个结点编码的类ui

private void output(HNode node) {

        if (node.lChild == null && node.rChild == null) {
            System.out.println(node.data + ": " + node.code);
        }
        if (node.lChild != null) {
            output(node.lChild);
        }
        if (node.rChild != null) {
            output(node.rChild);
        }
    }

    /**
     * 输出结果字符的哈夫曼编码
     */
    public void output() {
        output(root);
    }

这是编码的过程,主要原理是经过经过一个查找的方法不断地将传进的字符串在哈夫曼树中找到并返回他的哈夫曼编码最后在输出出来。this

/**
     * 编码
     * @param str
     * @return
     */
    public String toHufmCode(String str) {

        for (int i = 0; i < str.length(); i++) {
            String c = str.charAt(i) + "";
            search(root, c);
        }

        return hfmCodeStr;
    }

    /**
     *
     * @param root 哈夫曼树根节点
     * @param c 须要生成编码的字符
     */
    private void search(HNode root, String c) {
        if (root.lChild == null && root.rChild == null) {
            if (c.equals(root.data)) {
                hfmCodeStr += root.code; // 找到字符,将其哈夫曼编码拼接到最终返回二进制字符串的后面
            }
        }
        if (root.lChild != null) {
            search(root.lChild, c);
        }
        if (root.rChild != null) {
            search(root.rChild, c);
        }
    }

解码部分与之相似。编码

-     // 保存解码的字符串
    String result="";
    boolean target = false; // 解码标记
    /**
     * 解码
     * @param codeStr
     * @return
     */
    public String CodeToString(String codeStr) {

        int start = 0;
        int end = 1;

        while(end <= codeStr.length()){
            target = false;
            String s = codeStr.substring(start, end);
            matchCode(root, s); // 解码
            // 每解码一个字符,start向后移
            if(target){
                start = end;
            }
            end++;
        }

        return result;
    }

    /**
     * 匹配字符哈夫曼编码,找到对应的字符
     * @param root 哈夫曼树根节点
     * @param code 须要解码的二进制字符串
     */
    private void matchCode(HNode root, String code){
        if (root.lChild == null && root.rChild == null) {
            if (code.equals(root.code)) {
                result += root.data; // 找到对应的字符,拼接到解码字符穿后
                target = true; // 标志置为true
            }
        }
        if (root.lChild != null) {
            matchCode(root.lChild, code);
        }
        if (root.rChild != null) {
            matchCode(root.rChild, code);
        }

    }

至于我上传的测试类中的统计几率的方法是借鉴的李馨雨同窗的。
文件读写就太简单了相信学长学姐们都懂。.net

码云连接

感悟

哈夫曼是一种用于压缩的算法,是一种很实用的算法,但多是我的能力的限制,在具体实现过程当中碰见了不少困难,在具体的代码实现中,借鉴了不少网页和同窗的思路。

参考资料

相关文章
相关标签/搜索