Java 最优二叉树的哈夫曼算法的简单实现

Java 最优二叉树的哈夫曼算法的简单实现

一、哈夫曼编码算法简介

哈夫曼编码(Huffman coding)是一种无损压缩编码,广泛用于数据的压缩和传输。哈夫曼编码利用字符出现的频率进行编码,出现频率高的字符对应的编码短,反之出现频率低的字符对应的编码长,从而达到减少数据存储空间和传输带宽的目的。

哈夫曼编码的核心思想是构造哈夫曼树,将出现频率高的字符作为叶子节点,出现频率低的字符作为离叶子节点较近的节点,以此构造一个最优二叉树来实现编码。

二、哈夫曼编码算法过程

下面是哈夫曼编码算法的详细步骤:

1. 统计频率

遍历待编码的字符串,统计每个字符出现的频率,并存储到频率表中。

2. 构造哈夫曼树

通过频率表构造哈夫曼树:将出现频率低的字符作为左子树,出现频率高的字符作为右子树,从而构造出最优二叉树。

3. 生成编码表

以根节点为起点,向下递归遍历整个哈夫曼树,对于每个叶子节点记录它们的哈夫曼编码,生成编码表。

4. 编码数据

利用生成的编码表对数据进行编码,将原字符串中的字符依次转换成对应的哈夫曼编码串。

5. 解码数据

利用生成的哈夫曼树对哈夫曼编码串进行解码,将哈夫曼编码串转换成原字符串中的字符。

三、Java 实现哈夫曼编码算法

下面是使用 Java 语言实现哈夫曼编码算法的简单示例:


import java.util.*;
public class HuffmanCoding {

    /**
    * 统计字符出现的频率
    *
    * @param data 待编码的字符串
    * @return Map<Character, Integer> 每个字符出现的频率
    */
    public static Map<Character, Integer> getFrequency(String data) {
        Map<Character, Integer> frequency = new HashMap<Character, Integer>();
        for (int i = 0; i < data.length(); i++) {
            char c = data.charAt(i);
            if (frequency.containsKey(c)) {
                frequency.put(c, frequency.get(c) + 1);
            } else {
                frequency.put(c, 1);
            }
        }
        return frequency;
    }

    /**
    * 构造哈夫曼树
    *
    * @param frequency 每个字符出现的频率
    * @return Node 哈夫曼树的根节点
    */
    public static Node buildHuffmanTree(Map<Character, Integer> frequency) {
        PriorityQueue<Node> queue = new PriorityQueue<Node>();

        // 初始化节点队列
        for (Character c : frequency.keySet()) {
            queue.offer(new Node(c, frequency.get(c), null, null));
        }

        // 构建哈夫曼树
        while (queue.size() > 1) {
            Node leftNode = queue.poll();
            Node rightNode = queue.poll();
            Node parent = new Node(null, leftNode.frequency + rightNode.frequency, leftNode, rightNode);
            queue.offer(parent);
        }

        // 返回根节点
        return queue.poll();
    }

    /**
    * 遍历哈夫曼树,生成编码表
    *
    * @param root 哈夫曼树的根节点
    * @return Map<Character, String> 每个字符对应的哈夫曼编码
    */
    public static Map<Character, String> getCodingMap(Node root) {
        Map<Character, String> codingMap = new HashMap<Character, String>();
        getCode(root, "", codingMap);
        return codingMap;
    }

    private static void getCode(Node node, String code, Map<Character, String> codingMap) {
        if (node.isLeaf()) {
            codingMap.put(node.data, code);
            return;
        }
        getCode(node.left, code + "0", codingMap);
        getCode(node.right, code + "1", codingMap);
    }

    /**
    * 对数据进行编码
    *
    * @param data 待编码的字符串
    * @param codingMap 每个字符对应的哈夫曼编码
    * @return String 哈夫曼编码串
    */
    public static String encode(String data, Map<Character, String> codingMap) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < data.length(); i++) {
            sb.append(codingMap.get(data.charAt(i)));
        }
        return sb.toString();
    }

    /**
    * 对哈夫曼编码串进行解码
    *
    * @param code 哈夫曼编码串
    * @param root 哈夫曼树的根节点
    * @return String 解码后的字符串
    */
    public static String decode(String code, Node root) {
        StringBuilder sb = new StringBuilder();
        Node node = root;
        for (int i = 0; i < code.length(); i++) {
            char c = code.charAt(i);
            node = (c == '0') ? node.left : node.right;
            if (node.isLeaf()) {
                sb.append(node.data);
                node = root;
            }
        }
        return sb.toString();
    }

    /**
    * 节点类
    */
    static class Node implements Comparable<Node> {
        Character data;             // 节点值(叶子节点才有值)
        Integer frequency;          // 节点出现频率
        Node left;                  // 左子节点
        Node right;                 // 右子节点

        public Node(Character data, Integer frequency, Node left, Node right) {
            this.data = data;
            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;
        }
    }

    public static void main(String[] args) {
        String data = "hello world, i am a Java program.";
        Map<Character, Integer> frequency = getFrequency(data);
        Node root = buildHuffmanTree(frequency);
        Map<Character, String> codingMap = getCodingMap(root);
        String code = encode(data, codingMap);
        String text = decode(code, root);
        System.out.println("原数据:" + data);
        System.out.println("编码后:" + code);
        System.out.println("解码后:" + text);
    }
}

四、示例

  1. 编码示例:

待编码字符串:hello world, i am a Java program.

统计字符出现频率:{r=2, a=4, ,=6, h=1, d=1, e=1, J=1, g=1, l=3, o=4, i=1, m=2, n=1, p=1, W=1, .=1}

构造哈夫曼树:

               *
             /
            21
           /  \
         /      \
        8        *
       / \      / \
      /   \    /   \
     3     * 17    4
          / \
         /   \
        7     10
       / \   /  \
      2   5 7    3

生成编码表:

{ =111, a=00, .=1000, d=1111, e=1001, g=1011, h=11011, i=10100, J=110100, l=010, m=1100, n=10101, o=01, p=110101, r=011, W=110001}

编码结果:

`110111010100101100111110 …

  1. 解码示例:

待解码字符串: 110111010100101100111110 … (同上)

哈夫曼树(同上)

解码结果:

hello world, i am a Java program.

通过上述示例,我们可以清晰地了解哈夫曼编码算法的实现过程和原理,并且也能够准确地对数据进行编码和解码。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java 最优二叉树的哈夫曼算法的简单实现 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • 使用jpa之动态插入与修改(重写save)

    下面是使用JPA动态插入与修改的完整攻略: 1. 动态插入与修改简介 Java Persistence API(JPA)是JavaEE标准中的一个API规范,主要用于对象关系映射(ORM),方便程序开发人员通过面向对象的方式来操作关系型数据库。在使用JPA进行数据持久化时,我们通常需要使用一些注解来标记实体类,以及一个Repository来进行数据访问操作。…

    Java 2023年6月15日
    00
  • java编译命令基础知识点

    下面就来详细讲解一下Java编译命令的基础知识点,本次讲解分为以下几个部分: Java编译命令介绍 Java编译命令参数解释 Java编译命令示例 Java编译命令介绍 Java编译命令是指使用Java命令行工具(Command Prompt、Terminal等)来将Java源文件编译成可执行的Java字节码文件的命令。 Java编译命令的格式为:javac…

    Java 2023年5月20日
    00
  • Java实现指定目录下的文件查找详解

    下面开始讲解“Java实现指定目录下的文件查找详解”的攻略。 1. 需求背景 很多时候,我们需要查找指定目录下的某个或某些文件,这时候我们可以借助Java提供的API来实现。本文主要讲解Java如何实现指定目录下的文件查找。 2. 实现步骤 具体实现步骤如下: 2.1. 获取目录下所有的文件和子目录 我们可以使用Java提供的File类的listFiles(…

    Java 2023年5月19日
    00
  • 基于EJB技术的商务预订系统的开发

    开发基于EJB技术的商务预订系统可以分为以下几个步骤: 1. 需求分析和系统设计 在需求分析和系统设计阶段,需要考虑以下因素: 系统的功能需求,例如用户登录、商品展示、购物车管理、订单管理、支付管理等; 系统的性能需求,例如用户并发量、数据处理量、响应时间、可靠性等; 系统的架构设计,例如服务器端容器的选择、数据库的设计、系统的分层设计等。 示例1:用户登录…

    Java 2023年6月15日
    00
  • Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码

    Java 11 正式发布,这 8 个逆天新特性教你写出更牛逼的代码 Java 11于2018年9月正式发布,带来了一些令人兴奋的新特性和功能。在本文中,我们将介绍Java 11的八个强大的新特性,并给出一些示例,以帮助您更好地理解它们的使用方式。 1. HttpClient API Java 11引入了一个全新的HTTP客户端API,该API支持异步和基于事…

    Java 2023年5月20日
    00
  • AOT的作用是什么?

    当谈到AOT时,我们通常指的是AoT编译,即Ahead-of-Time编译技术。以下是AOT的作用以及如何使用它的完整攻略。 AOT的作用 AOT编译技术是指在应用程序部署之前,将应用程序的代码转换成本地可执行代码的过程。AOT的主要作用在于: 提高应用程序的性能:与JIT(Just-in-Time)编译器相比,AOT编译器将应用程序的代码在部署时即转换成本…

    Java 2023年5月11日
    00
  • 详解Springboot配置文件的使用

    下面是“详解Springboot配置文件的使用”的完整攻略。 什么是Springboot配置文件? Springboot的配置文件是一个以properties或yml为扩展名的文件,用于配置Springboot应用程序的参数。 在Springboot中,我们可以通过配置文件来轻松地配置应用程序的各种参数,例如:端口号、数据源、日志、邮件等等。 配置文件的使用…

    Java 2023年5月15日
    00
  • java自己手动控制kafka的offset操作

    当使用kafka作为消费者时,消费者往往需要对消费的offset进行管理,以确保以后能够正确地读取数据。我们通常使用kafka内置的自动提交offset机制,但有时候我们也需要手动控制offset。 下面是一些步骤和示例,让你更好地了解如何手动控制kafka的offset操作: 步骤1:创建kafka消费者 首先,我们需要创建kafka消费者。以下是创建一个…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部