Java实现哈夫曼编码

一、内容

1、内部类

        1> 哈夫曼树节点类型:HuTNode.class

        2> 封装节点在底层数组的下标和对应的权重的类:IndexAndWeight.class

        3> 封装字符和对应哈夫曼编码的类:HuCode.class

2、方法

        1> 构造哈夫曼树:CreatHuTree()

        2> 在合并节点时,选择权重最小和次小的两个节点:selectIndexOfMinWeight(HuTNode ht)

        3> 根据构造的哈夫曼树,获取叶子节点对应字符的哈夫曼编码:getHuCode(HuTNode ht)

        4> 根据得到的哈夫曼编码集,将字符串转换成0101串:String2HuffmanCode(String source, String huffmanCode)

        5> 根据得到的哈夫曼编码集,将0101串转换成字符串:HuffmanCode2String(String huffmanCode, String source)

二、代码

public class HuTree {

    public static void main(String[] args) {
        // 构建哈夫曼树
        HuTNode[] ht = creatHuTree();

        // 输出构造好的哈夫曼树
        System.out.println("\n===============输出构造好的哈夫曼树===================");
        System.out.println("下标\t\t数据\t\t权重\t\t双亲\t\t左孩\t\t右孩");
        System.out.println("0\t\t-\t\t-\t\t-\t\t-\t\t-\t\t");
        for (int i=1; i<ht.length; ++i){
            System.out.print(i + "\t\t");
            System.out.println(ht[i]);
        }

        // 根据哈夫曼树ht,将每个给定字符及其哈夫曼编码存储到一个HuCode类型数组中
        HuCode[] hc = getHuCode(ht);

        // 输出哈夫曼编码集hc
        System.out.println("\n===============哈夫曼编码集===================");
        System.out.println("数据\t\t哈夫曼码");
        for (HuCode code: hc){
            System.out.println(code);
        }

        // 给定字符串,根据上述哈夫曼编码转换成0101串
        String source = "A_Tree";
        String huffmanCode = String2HuffmanCode(source, hc);
        System.out.println("\n===============【A_Tree】对应的0101串===================");
        System.out.println(huffmanCode);

        // 解码由上述哈夫曼编码组成的0101串
        huffmanCode = "00100011000001";
        source = huffmanCode2String(huffmanCode, hc);
        System.out.println("\n===============【00100011000001】对应的字符串===================");
        System.out.println(source);

        huffmanCode = "00111001";
        source = huffmanCode2String(huffmanCode, hc);
        System.out.println("\n===============【00111001】对应的字符串===================");
        System.out.println(source);
    }


    /**
     * 功能:构造哈夫曼树
     * 说明:该哈夫曼树使用顺序存储的方式存储在一个HuTNode类型的数组中,且该数组0号下标不使用
     * @return 返回根据给定节点和权重构造的哈夫曼树
     */
    private static HuTNode[] creatHuTree() {
        // 输入控制
        Scanner in = new Scanner(System.in);
        System.out.print("请输入各叶子节点数据域的值,以空格分隔:");
        String[] nodes = in.nextLine().split(" ");
        System.out.print("请输入各叶子节点对应的权重:");
        String[] weights = in.nextLine().split(" ");

        // 初始化哈夫曼树
        int n = nodes.length;// 带权叶子节点数
        int m = 2*n;// 由于底层数组0号位置不存放节点,所以n个叶子节点合并生成哈夫曼树后,需要2n-1+1,即2n长度的数组
        HuTNode[] ht = new HuTNode[m];
        for (int i=0; i<n; ++i){
            HuTNode newNode = new HuTNode(nodes[i].charAt(0), Integer.valueOf(weights[i]));
            ht[i+1] = newNode;
        }

        // 选择权重最小的节点两两合并构造哈夫曼树
        for (int i=0; i<n-1; ++i){// n个叶子节点需要合并n-1次
            int[] arr = selectIndexOfMinWeight(ht);// 获取权重最小的两个节点的下标

            // 创建合并的新节点
            HuTNode newNode = new HuTNode();
            newNode.lChild = arr[0];
            newNode.rChild = arr[1];
            newNode.weight = ht[arr[0]].weight + ht[arr[1]].weight;
            ht[n+i+1] = newNode;// 此时底层数组中有效节点有n+i个,将新生成的节点保存在数组中时,下标应为n+i+1

            // 修改被合并节点的双亲域
            ht[arr[0]].parent = n+i+1;
            ht[arr[1]].parent = n+i+1;
        }

        return ht;
    }


    /**
     * 在所有节点双亲域的值是0节点中,选择两个权重最小的节点,并将他们在底层数组中的下标打包成数组进行返回
     * @param ht 底层数组
     * @return 最小权重节点的下标打包成的数组
     */
    private static int[] selectIndexOfMinWeight(HuTNode[] ht) {
        int[] arr = new int[2];

        List<IndexAndWeight> list = new LinkedList<>();
        int i = 1;
        while (ht[i] != null){
            if (ht[i].parent != 0){// 过滤掉存在双亲的节点
                ++i;
                continue;
            }

            list.add( new IndexAndWeight(i, ht[i].weight) );
            ++i;
        }

        // 根据权重从从小到大排序
        list.sort(new Comparator<IndexAndWeight>() {
            @Override
            public int compare(IndexAndWeight o1, IndexAndWeight o2) {
                if (o1.weight > o2.weight){
                    return 1;
                }else {
                    return -1;
                }
            }
        });

        arr[0] = list.get(0).index;
        arr[1] = list.get(1).index;

        return arr;
    }


    /**
     * 根据哈夫曼树ht,将每个给定字符及其哈夫曼编码存储到一个HuCode类型数组中
     * @param ht 哈夫曼树
     * @return 字符和对应哈夫曼编码组成的数组,即哈夫曼编码集
     */
    private static HuCode[] getHuCode(HuTNode[] ht) {
        int n = (ht.length+1)/2;// 叶子节点个数
        HuCode[] hc = new HuCode[n];


        // 从每个叶子节点回溯找其双亲节点,并根据每一次回溯的路径,保存0或1,知道找到根节点为止
        Stack<Character> stack = new Stack<>();// 用于保存回溯得到的0或1
        for (int i=1; i<=n; ++i){
            stack.clear();

            // 回溯,并将0或1入栈
            HuTNode child = ht[i];// 叶子节点
            int childIndex = i;
            while (child.parent != 0){
                int parentIndex = child.parent;
                HuTNode parent = ht[parentIndex];

                if (parent.lChild == childIndex){// child是parent的左孩子,将0入栈
                    stack.push('0');
                }else {// child是parent的右孩子,将1入栈
                    stack.push('1');
                }

                child = ht[child.parent];
                childIndex = parentIndex;
            }

            // stack按出栈顺序组成对应字符的哈夫曼编码,
            StringBuilder code = new StringBuilder("");
            while (!stack.isEmpty()){
                code.append(stack.pop());
            }

            // 保存在hc数组中
            hc[i-1] = new HuCode(ht[i].data, code.toString());
        }

        return hc;
    }


    /**
     * 根据给定的哈夫曼编码集,将指定字符串编码成0101串
     * @param source 字符串
     * @param hc 哈夫曼编码集
     * @return 0101串
     */
    private static String String2HuffmanCode(String source, HuCode[] hc) {
        StringBuilder huffmanCode = new StringBuilder("");

        for (int i=0; i<source.length(); ++i){
            char c = source.charAt(i);

            boolean flag = false;
            for (HuCode huCode: hc){
                if (huCode.data == c){
                    huffmanCode.append(huCode.code);
                    flag = true;// 表示编码成功
                }
            }

            if (!flag){
                throw new RuntimeException("【" + c + "】不存在对应的哈夫曼编码");
            }
        }

        return huffmanCode.toString();
    }


    /**
     * 根据给定的哈夫曼编码集,将指定0101串解码成字符串
     * @param huffmanCode 0101串
     * @param hc 哈夫曼编码集
     * @return 字符串
     */
    private static String huffmanCode2String(String huffmanCode, HuCode[] hc) {
        StringBuilder source = new StringBuilder("");
        StringBuilder tempCode = new StringBuilder("");

        for (int i=0; i<huffmanCode.length(); ++i){
            tempCode.append(huffmanCode.charAt(i));
            // 判断当前tempCode是否在哈夫曼编码集中
            for (HuCode huCode: hc){
                if ( huCode.code.equals(tempCode.toString()) ){
                    tempCode.delete(0, tempCode.length());// 清空tempCode
                    source.append(huCode.data);
                }
            }
        }

        if (!tempCode.toString().equals("")){
            throw new RuntimeException("【" + huffmanCode + "】解码失败!");
        }

        return source.toString();
    }


    //数组元素类(哈夫曼树节点类)
    static class HuTNode{
        char data;// 数据域
        int weight;// 权重
        int parent;// 双亲
        int lChild;// 左孩子
        int rChild;// 右孩子

        public HuTNode(char data, int weight) {
            this.data = data;
            this.weight = weight;
        }

        public HuTNode() {
        }

        @Override
        public String toString() {
            return this.data + "\t\t" + this.weight + "\t\t" + this.parent + "\t\t" + this.lChild + "\t\t" + this.rChild;
        }
    }

    // 用于合并节点时,筛选出权重最小两个节点用的内部类,封装了节点在底层数组中的下标,以及权重
    static class IndexAndWeight{
        int index;// 下标
        int weight;// 权重

        public IndexAndWeight(int index, int weight) {
            this.index = index;
            this.weight = weight;
        }
    }

    // 编码类,封装了字符及对应的哈夫曼编码
    static class HuCode{
        char data;
        String code;

        public HuCode(char data, String code) {
            this.data = data;
            this.code = code;
        }

        @Override
        public String toString() {
            return data + "\t\t" + code;
        }
    }
}

三、测试

 

  • 8
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈夫曼编码是一种常见的压缩算法,可以将一个字符串或文件转换为一个较小的二进制编码。以下是使用Java实现哈夫曼编码的示例代码: ```java import java.util.*; public class HuffmanCoding { private static class Node implements Comparable<Node> { int frequency; char character; Node left, right; public Node(char character, int frequency, Node left, Node right) { this.character = character; this.frequency = frequency; this.left = left; this.right = right; } public boolean isLeaf() { return left == null && right == null; } @Override public int compareTo(Node node) { return frequency - node.frequency; } } private static Map<Character, String> encode(String text) { // 统计字符频率 Map<Character, Integer> frequencyMap = new HashMap<>(); for (char c : text.toCharArray()) { frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); } // 构建哈夫曼树 Queue<Node> queue = new PriorityQueue<>(); for (char c : frequencyMap.keySet()) { queue.add(new Node(c, frequencyMap.get(c), null, null)); } while (queue.size() > 1) { Node left = queue.poll(); Node right = queue.poll(); queue.add(new Node('\0', left.frequency + right.frequency, left, right)); } Node root = queue.poll(); // 构建编码表 Map<Character, String> encodingMap = new HashMap<>(); buildEncodingMap(root, "", encodingMap); // 编码文本 Map<Character, String> encodedText = new HashMap<>(); for (char c : text.toCharArray()) { encodedText.put(c, encodingMap.get(c)); } return encodedText; } private static void buildEncodingMap(Node node, String code, Map<Character, String> map) { if (node.isLeaf()) { map.put(node.character, code); } else { buildEncodingMap(node.left, code + "0", map); buildEncodingMap(node.right, code + "1", map); } } public static void main(String[] args) { String text = "hello world"; Map<Character, String> encodingMap = encode(text); System.out.println("Encoding Map: " + encodingMap); StringBuilder encodedText = new StringBuilder(); for (char c : text.toCharArray()) { encodedText.append(encodingMap.get(c)); } System.out.println("Encoded Text: " + encodedText); } } ``` 在上面的示例中,我们首先统计了给定文本中每个字符的频率。然后,我们使用优先队列(最小堆)构建哈夫曼树,其中每个节点都包含一个字符、其频率以及左右子节点。接下来,我们使用递归方式构建编码表,其中每个字符都映射到一个二进制编码。最后,我们使用编码表将原始文本转换为二进制编码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值