算法系列之数据结构-Huffman树

简介: Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。

_20250306_190203.png

在数据压缩领域,Huffman编码是一种经典的无损压缩算法,而Huffman树则是实现这种编码的关键数据结构。它以其高效性和简洁性被广泛应用于各种场景,从文件压缩到通信协议,都离不开Huffman树的身影。本文将深入探讨Huffman树的原理、构建过程以及其Java如何实现Huffman树。

Huffman树的构建步骤

Huffman树(哈夫曼树)又称最优二叉树,是一种带权路径长度最短的二叉树,常用于信息传输、数据压缩等方面。它的构造基于字符出现的频率,通过将频率较低的字符组合在一起,最终形成一棵树。在Huffman树中,每个叶节点代表一个字符,而每个字符的编码则是从根节点到叶节点的路径所对应的二进制序列。

以下是Huffman树的构建步骤:

  1. 统计字符频率:统计待压缩数据中每个字符出现的频率。

  2. 构建优先队列:将每个字符及其频率作为一个节点,放入优先队列(最小堆)中。

  3. 合并节点:从优先队列中取出两个频率最小的节点,合并成一个新的节点,新节点的频率为这两个节点的频率之和,然后将新节点放回优先队列。

  4. 重复合并:重复步骤 3,直到优先队列中只剩一个节点,这个节点就是 Huffman 树的根节点。

  5. 生成编码:从根节点开始,向左走为 0,向右走为 1,遍历整棵树,生成每个字符的 Huffman 编码。

比如有字符串及频率集合:{a: 5,b: 9,c: 12,d: 13,e: 36},下图为构建过程:

_20250306_180610.png

Huffman 解码

Huffman生成编码之后我们可以用Huffman的编码代替字符串,达到数据压缩的操作。反过来,我们要解码的时候也可以由Huffman树便捷的完成。解码过程:从头开始扫描二进制编码串,从Huffman树的根节点开始,根据比特位不断地进入下一层节点,当遇到0时进入左子节点,遇到1时进入右子节点;到达叶子节点后输出对应的字符,然后重复次步骤,直到二进制串完毕,则可以得到解码之后的字符串。

Java实现Huffman树

以下是由java实现的Huffman树的构建、编码及解码。

/**
 * Huffman树节点实体类
 */
@Data
public class HuffmanTreeNode implements Comparable<HuffmanTreeNode>{
   
    //字符
    private char data;
    //权重
    private int weight;
    //左子节点
    private HuffmanTreeNode left;
    //右子节点
    private HuffmanTreeNode right;

    //需要对权重进行排序,重写compareTo接口
    @Override
    public int compareTo(HuffmanTreeNode o) {
   
        if(o.getWeight() > this.getWeight()){
   
            return 1;
        }
        if(o.getWeight() < this.getWeight()){
   
            return -1;
        }
        return 0;
    }
    public HuffmanTreeNode(char data, int weight) {
   
        this.data = data;
        this.weight = weight;
    }
}



/**
 * Huffman树
 */
public class HuffmanTreeExample {
   

    /**
     * 构建Huffman树
     * 使用优先队列来构建Huffman树
     */
    public static HuffmanTreeNode build(Map<Character, Integer> charMap) {
   
        // 将字符频率表中的每个字符及其频率作为节点加入优先队列
        PriorityQueue<HuffmanTreeNode> queue = new PriorityQueue<>();
        for(Map.Entry<Character, Integer> entry : charMap.entrySet()){
   
            queue.add(new HuffmanTreeNode(entry.getKey(), entry.getValue()));
        }
        // 循环构建Huffman树,当队列中只剩一个节点时,Huffman树构建完成,此节点为Huffman的根节点
        while(queue.size() > 1){
   
            // 取出两个频率最小的节点
            HuffmanTreeNode left = queue.poll();
            HuffmanTreeNode right = queue.poll();
            // 将两个节点合并为一个父节点,父节点的频率为两个子节点的频率之和
            HuffmanTreeNode parent = new HuffmanTreeNode('\0', left.getWeight() + right.getWeight());
            parent.setLeft(left);
            parent.setRight(right);
            // 将父节点加入队列
            queue.add(parent);
        }

        //构建完成,返回根节点
        return queue.poll();
    }

    /**
     * 生成Huffman编码
     * @param root,节点
     * @param code,当前节点编码
     * @param huffmancode 编码map
     */
    public static void generateHuffmanCodes(HuffmanTreeNode root, String code,Map<Character,String> huffmancode) {
   
        //根节点为空则返回
        if(root == null){
   
            return;
        }
        if(root.getLeft() == null && root.getRight() == null){
   
            //叶子节点
            huffmancode.put(root.getData(),code);
        }else{
   
            //非叶子节点,递归遍历左右子树
            generateHuffmanCodes(root.getLeft(),code + "0",huffmancode);
            generateHuffmanCodes(root.getRight(),code + "1",huffmancode);
        }
    }

    /**
     * Huffman编码
     */
    public static String encode(String str, Map<Character, String> huffmancode) {
   
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
   
            sb.append(huffmancode.get(str.charAt(i)));
        }
        return sb.toString();
    }

    /**
     * Huffman解码
     */
    public static String decode(String str, HuffmanTreeNode root) {
   
        StringBuilder sb = new StringBuilder();
        HuffmanTreeNode node = root;
        // 遍历字符串,根据字符对应的编码,找到对应的叶子节点,输出字符
        for (int i = 0; i < str.length(); i++) {
   
            if (str.charAt(i) == '0') {
   
                node = node.getLeft();
            } else {
   
                node = node.getRight();
            }
            //叶子节点,输出字符,重置node为根节点
            if (node.getLeft() == null && node.getRight() == null) {
   
                sb.append(node.getData());
                node=root;
            }
        }
        return sb.toString();
    }



    public static void main(String[] args) {
   
        String text = "hello world";
        //统计字符出现频率
        Map<Character, Integer> charMap = new HashMap<>();
        for (char c : text.toCharArray()) {
   
            charMap.put(c, charMap.getOrDefault(c, 0) + 1);
        }
        //构建Huffman树
        HuffmanTreeNode root = build(charMap);
        //获取Huffman编码
        Map<Character, String> huffmancode = new HashMap<>();
        generateHuffmanCodes(root, "", huffmancode);
        System.out.println("Huffman编码:"+huffmancode.toString());
        //对字符串进行Huffman编码
        String encodeStr = encode(text, huffmancode);
        System.out.println("Huffman编码后的字符串:"+encodeStr);
        //对字符串进行Huffman解码
        String decodeStr = decode(encodeStr, root);
        System.out.println("Huffman解码后的字符串:"+decodeStr);


    }
}

Huffman 树的应用

Huffman 树主要用于数据压缩领域,常见的应用场景包括:

  • 文件压缩:如 ZIP、GZIP 等压缩工具中使用了 Huffman 编码。

  • 图像压缩:如 JPEG 图像格式中使用了 Huffman 编码。

  • 网络传输:在数据传输过程中,使用 Huffman 编码可以减少数据量,提高传输效率。

总结

Huffman 树是一种高效的数据压缩算法,通过构建带权路径长度最短的二叉树来实现数据压缩。Java 中可以通过优先队列和递归的方式实现 Huffman 树的构建和编码生成。Huffman 树在文件压缩、图像压缩和网络传输等领域有广泛的应用。

目录
相关文章
|
1月前
|
人工智能 算法 语音技术
Video-T1:视频生成实时手术刀!清华腾讯「帧树算法」终结闪烁抖动
清华大学与腾讯联合推出的Video-T1技术,通过测试时扩展(TTS)和Tree-of-Frames方法,显著提升视频生成的连贯性与文本匹配度,为影视制作、游戏开发等领域带来突破性解决方案。
80 4
Video-T1:视频生成实时手术刀!清华腾讯「帧树算法」终结闪烁抖动
|
1月前
|
存储 算法 Java
算法系列之数据结构-二叉树
树是一种重要的非线性数据结构,广泛应用于各种算法和应用中。本文介绍了树的基本概念、常见类型(如二叉树、满二叉树、完全二叉树、平衡二叉树、B树等)及其在Java中的实现。通过递归方法实现了二叉树的前序、中序、后序和层次遍历,并展示了具体的代码示例和运行结果。掌握树结构有助于提高编程能力,优化算法设计。
60 9
 算法系列之数据结构-二叉树
|
1月前
|
存储 自然语言处理 数据库
【数据结构进阶】AVL树深度剖析 + 实现(附源码)
在深入探讨了AVL树的原理和实现后,我们不难发现,这种数据结构不仅优雅地解决了传统二叉搜索树可能面临的性能退化问题,还通过其独特的平衡机制,确保了在任何情况下都能提供稳定且高效的查找、插入和删除操作。
113 19
|
1月前
|
算法 Java
算法系列之数据结构-二叉搜索树
二叉查找树(Binary Search Tree,简称BST)是一种常用的数据结构,它能够高效地进行查找、插入和删除操作。二叉查找树的特点是,对于树中的每个节点,其左子树中的所有节点都小于该节点,而右子树中的所有节点都大于该节点。
85 22
|
2月前
|
存储 机器学习/深度学习 算法
C 408—《数据结构》算法题基础篇—链表(下)
408考研——《数据结构》算法题基础篇之链表(下)。
114 29
|
2月前
|
存储 算法 C语言
C 408—《数据结构》算法题基础篇—链表(上)
408考研——《数据结构》算法题基础篇之链表(上)。
144 25
|
5月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
582 9
|
5月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
146 58
|
3月前
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
242 77
|
2月前
|
算法 调度 C++
STL——栈和队列和优先队列
通过以上对栈、队列和优先队列的详细解释和示例,希望能帮助读者更好地理解和应用这些重要的数据结构。
39 11
下一篇
oss创建bucket