哈夫曼编码实现对文件的编码解码java语言

一、简单说一下Huffman:

用Java语言写一个用哈夫曼编码对任意一个文件(.txt)的内容进行编解码的程序。

(学了数据结构!懂!)
哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最 佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)

简单来说,若在一个字符串中,知道每个字母各自出现的频率,通过将出现频率较大的字符采用较少位数来编码的方式达到压缩的目的,即一个字符出现的频次越大,它的编码位数应该更少。

二、结果、代码、说明:

①在自行建立input.txt文件任意写入内容(中文数字字母等,记得要建在同一个文件夹目录)
②运行后,可以在自行建立的input.txt文件里查看到:
(1)编码前内容、(2)编码表、(3)解码后内容
运行结果
文件夹内容

//哈夫曼编码实现对文件的编码解码
package com;  //建立package为com
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.*;
import org.junit.Test;
import java.io.*;

public class Huffman {
    //内部类 二叉树节点
    private class TreeNode {
        public TreeNode() { }
        public TreeNode(Character ch, int val, int freq, TreeNode left, TreeNode right) {
            this.ch = ch;
            this.val = val;
            this.freq = freq;
            this.left = left;
            this.right = right;
        }
        Character ch;
        int val;
        int freq;
        TreeNode left;
        TreeNode right;
    }

    @Test
    public void testEncode(){

        /*读取txt文本*/
        String pathname = "input.txt";
        // 绝对路径或相对路径都可以,写入文件时演示相对路径,读取以上路径的input.txt文件
        try (FileReader reader = new FileReader(pathname);
             BufferedReader br = new BufferedReader(reader) // 建立一个对象,
        ) {
            String s = null;// = "aaabbbeeedacfwwwwddd";
            //String line;
            while ((s = br.readLine()) != null) {
                // 一次读入一行数据
                System.out.println("编码前:"+s);
                Object[] encodeRes = encode(s);
                String encodeStr = (String)encodeRes[0];
                Map<Character,String> encodeMap = (Map<Character, String>)encodeRes[1];

                System.out.println("编码表:");
                for(Map.Entry<Character,String> e:encodeMap.entrySet()){
                    System.out.println(e.getKey()+":"+e.getValue());
                }
                System.out.println("编码后:"+encodeStr);

                String decodeStr = decode(encodeStr,encodeMap);
                System.out.println("解码后:"+decodeStr);

                //写入文本
                try {
                    File writeName = new File("output.txt"); // 相对路径,如果没有则要建立一个新的output.txt文件
                    writeName.createNewFile(); // 创建新文件,有同名的文件的话直接覆盖
                    try (FileWriter writer = new FileWriter(writeName);
                         BufferedWriter out = new BufferedWriter(writer)
                    ) {
                        out.write("--------原文本内容--------\r\n"); // \r\n即为换行
                        out.write(s);
                        out.write("\r\n\n");
                        out.write("---------哈夫曼编码输出---------\r\n"); // \r\n即为换行
                        out.write(encodeStr);
                        out.write("\r\n\n");
                        out.write("--------编码表--------\r\n"); // \r\n即为换行
                        for(Map.Entry<Character,String> e:encodeMap.entrySet()){
                            out.write(e.getKey()+":"+e.getValue());
                            out.write("\r\n");
                        }
                        out.write("\r\n\n");
                        out.write("--------解码输出--------\r\n"); // \r\n即为换行
                        out.write(decodeStr);
                        out.write("\r\n\n----2021----");
                        out.flush(); // 把缓存区内容压入文件
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //编码方法,返回Object[],大小为2,Objec[0]为编码后的字符串,Object[1]为编码对应的码表
    public Object[] encode(String s){
        Object[]res= new Object[2];
        Map<Character,String> encodeMap = new HashMap<Character, String>();
        TreeNode tree = constructTree(s);
        findPath(tree, encodeMap, new StringBuilder());
        findPath(tree, encodeMap, new StringBuilder());
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<s.length();i++){
            String tmp = encodeMap.get(s.charAt(i));
            sb.append(tmp);
        }
        res[0]=sb.toString();
        res[1] = encodeMap;
        return res;

    }

    /*
     * 根据字符串建立二叉树
     * @param s:要编码的源字符串
     */
    private TreeNode constructTree(String s) {
        if (s == null || s.equals("")) {
            return null;
        }
        //计算每个字母的词频,放到Map中
        Map<Character, Integer> dataMap = new HashMap<Character, Integer>();
        for (int i = 0; i < s.length(); i++) {
            Character c = s.charAt(i);
            if (dataMap.containsKey(c)) {
                int count = dataMap.get(c);
                dataMap.put(c, count + 1);
            } else {
                dataMap.put(c, 1);
            }
        }
        //遍历dataMap,初始化二叉树节点,并将所有初始化后的节点放到nodeList中,并进行排序
        LinkedList<TreeNode> nodeList = new LinkedList<TreeNode>();
        for (Map.Entry<Character, Integer> entry : dataMap.entrySet()) {
            Character ch = entry.getKey();
            int freq = entry.getValue();
            int val = 0;
            TreeNode tmp = new TreeNode(ch,val,freq,null,null);
            nodeList.add(tmp);
        }
        //对存放节点的链表进行排序,方便后续进行组合
        Collections.sort(nodeList, new Comparator<TreeNode>() {
            public int compare(TreeNode t1, TreeNode t2) {
                return t1.freq-t2.freq;
            }
        });

        //size==1,代表字符串只包含一种类型的字母
        if(nodeList.size()==1){
            TreeNode t = nodeList.get(0);
            return new TreeNode(null,0,nodeList.get(0).freq,t,null);
        }

        //利用排序好的节点建立二叉树,root为初始化根节点
        TreeNode root = null;
        while(nodeList.size()>0){
            //因为nodeList在前面已经排好序,所以直接取出前两个节点,他们的和肯定为最小
            TreeNode t1 = nodeList.removeFirst();
            TreeNode t2 = nodeList.removeFirst();
            //左子树的val赋值为0,右子树的val赋值为1
            t1.val = 0;
            t2.val = 1;
            //将取出的两个节点进行合并
            if(nodeList.size()==0){
                //此时代表所有节点合并完毕,返回结果
                root = new TreeNode(null,0,t1.freq+t2.freq,t1,t2);
            }else {
                //此时代表还有可以合并的节点
                TreeNode tmp = new TreeNode(null,0,t1.freq+t2.freq,t1,t2);

                //t1、t2合并后,需要将得到的新节点加入到原链表中,继续与其他节点合并,
                //此时需要保证原链表的有序性,需要进行排序
                if(tmp.freq>nodeList.getLast().freq){
                    nodeList.addLast(tmp);
                }else {
                    for(int i=0;i<nodeList.size();i++){
                        int tmpFreq = tmp.freq;
                        if(tmpFreq<= nodeList.get(i).freq){
                            nodeList.add(i,tmp);
                            break;
                        }
                    }
                }
            }
        }
        //返回建立好的二叉树根节点
        return root;
    }

    //对已经建立好的二叉树进行遍历,得到每个字符的编码
    private void findPath(TreeNode root, Map<Character,String> res, StringBuilder path) {
        if (root.left == null && root.right == null) {
            path.append(root.val);
            res.put(root.ch,path.substring(1));
            path.deleteCharAt(path.length() - 1);
            return;
        }
        path.append(root.val);
        if (root.left != null) findPath(root.left, res, path);
        if (root.right != null) findPath(root.right, res, path);
        path.deleteCharAt(path.length() - 1);
    }

    //对字符串进行解码,解码时需要编码码表
    public String decode(String encodeStr,Map<Character,String> encodeMap){
        StringBuilder decodeStr = new StringBuilder();
        while(encodeStr.length()>0){
            for(Map.Entry<Character,String> e: encodeMap.entrySet()){
                String charEncodeStr = e.getValue();
                if(encodeStr.startsWith(charEncodeStr)){
                    decodeStr.append(e.getKey());
                    encodeStr = encodeStr.substring(charEncodeStr.length());
                    break;
                }
            }
        }
        return decodeStr.toString();
    }
}

三、readMe内容:

***readMe:***
①软件:Java:下载安装环境配置教程(可直接下载最新版):
链接: https://blog.csdn.net/qq_45422588/article/details/105640508.
简单测试:直接在cmd运行第一个HelloWorld程序
链接: https://www.cnblogs.com/baoxiaofei/p/4116632.html.

②编辑器:IntelliJ IDEA
链接: https://blog.csdn.net/weixin_44519789/article/details/108023152.
(好像Eclipse更好! 下载链接: https://blog.csdn.net/weixin_43821795/article/details/100525115.)

(为了防止下次自己要用到又忘了,所以码出来,🤫)

小提示:
如果使用eclipse出现文本输出乱码
链接: https://blog.csdn.net/limenghua9112/article/details/50783679.
(或许如果有空也可以详细了解一下这几个家伙UTF-8、ANSI、GBK、GB2312 吧啦吧啦~)

这里是题外话(今天遇到一个超有感触的座右铭,记下来)
贵有恒,何必三更起五更睡;最无益,只怕一日曝十日寒。
努力,是为了将运气成分降到最低。

完结!
  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
哈夫曼编码是一种可变长度编码方式,它基于出现频率来分配短的编码给高频率出现的字符,从而实现压缩数据的目的。 贪心算法是一种优化问题解决方法,每一步都选择当前最优解,最终得到全局最优解。 使用贪心算法实现哈夫曼编码,可以按照以下步骤进行: 1. 统计每个字符出现的频率,并将它们存储在一个数组中。 2. 将频率数组转化为一个节点数组,每个节点表示一个字符及其频率。 3. 将节点数组按照频率从小到大排序。 4. 从节点数组中选取两个频率最小的节点,合并成一个新节点,并将新节点的频率设置为两个节点的频率之和。 5. 将新节点插入到节点数组中,并将节点数组按照频率从小到大排序。 6. 重复步骤 4 和步骤 5,直到节点数组中只剩下一个节点为止。 7. 从根节点开始,遍历哈夫曼树,对于每一个左分支,将编码设置为 0,对于每一个右分支,将编码设置为 1。 8. 将每个字符的编码存储在一个编码表中。 下面是使用 Java 语言实现哈夫曼编码的代码: ```java import java.util.*; public class Huffman { // 节点类 static class Node implements Comparable<Node> { char ch; // 字符 int freq; // 频率 Node left, right; // 左孩子和右孩子 // 构造函数 Node(char ch, int freq) { this.ch = ch; this.freq = freq; } // 比较方法 public int compareTo(Node other) { return this.freq - other.freq; } } // 构建哈夫曼树 public static Node buildHuffmanTree(int[] freq) { PriorityQueue<Node> pq = new PriorityQueue<Node>(); for (char i = 0; i < freq.length; i++) { if (freq[i] > 0) { pq.offer(new Node(i, freq[i])); } } while (pq.size() > 1) { Node left = pq.poll(); Node right = pq.poll(); Node parent = new Node('\0', left.freq + right.freq); parent.left = left; parent.right = right; pq.offer(parent); } return pq.poll(); } // 构建编码表 public static void buildCodes(Node root, String[] codes, StringBuilder sb) { if (root == null) { return; } if (root.ch != '\0') { codes[root.ch] = sb.toString(); } else { sb.append('0'); buildCodes(root.left, codes, sb); sb.deleteCharAt(sb.length() - 1); sb.append('1'); buildCodes(root.right, codes, sb); sb.deleteCharAt(sb.length() - 1); } } // 哈夫曼编码 public static String encode(String str, String[] codes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i++) { sb.append(codes[str.charAt(i)]); } return sb.toString(); } // 哈夫曼解码 public static String decode(String str, Node root) { StringBuilder sb = new StringBuilder(); Node curr = root; for (int i = 0; i < str.length(); i++) { if (str.charAt(i) == '0') { curr = curr.left; } else { curr = curr.right; } if (curr.ch != '\0') { sb.append(curr.ch); curr = root; } } return sb.toString(); } // 主函数 public static void main(String[] args) { String str = "hello world"; int[] freq = new int[256]; for (int i = 0; i < str.length(); i++) { freq[str.charAt(i)]++; } Node root = buildHuffmanTree(freq); String[] codes = new String[256]; buildCodes(root, codes, new StringBuilder()); String encodedStr = encode(str, codes); String decodedStr = decode(encodedStr, root); System.out.println("原始字符串:" + str); System.out.println("哈夫曼编码:" + encodedStr); System.out.println("哈夫曼解码:" + decodedStr); } } ``` 运行结果: ``` 原始字符串:hello world 哈夫曼编码:101001011100111000110101110000011011001011000011 哈夫曼解码:hello world ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值