前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >哈夫曼树与哈夫曼编码:聪明的数据压缩技术

哈夫曼树与哈夫曼编码:聪明的数据压缩技术

原创
作者头像
Lorin 洛林
发布2023-11-14 17:53:40
5041
发布2023-11-14 17:53:40
举报
文章被收录于专栏:数据结构数据结构
  • hello,大家好,我是 Lorin,今天给大家带来数据结构中,二叉树中的特殊类型-哈夫曼树,下面我们来看看什么是哈夫曼树以及它是如何实现数据存储和传输的压缩。

哈夫曼树(最优二叉树)

  • 给定N个权值作为N个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。
  • 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
  • 基本定义:
    • 权:赋予节点一些属性,如数量或权重,如 A 的权重为 2。
    • 路径:一棵树中,一个节点到另外相邻一个节点之间的通路称为路径,或者边。
    • 节点路径长度:在一棵树中,从一个节点到根节点所经历的路径或边的数量,我们称为节点路径长度,如 A 的路径长度为 3。
    • 节点的带权路径长度:一棵树中,每一个节点都有自己的权重,权重节点路径长度=节点的带权路径长度;如节点 A 的带权路径长度= 3 2 = 6。
    • 树的带权路径长度:所有节点的带权路径长度之和,如上述二叉树带权路径长度 = 2 * 3 + 1 * 3 + 3 * 2 + 1 * 3 + 1 * 3 + 2 * 2 = 25。

哈夫曼算法

  • 构建哈夫曼树的过程称为哈夫曼算法,核心思想是将权重越大的节点放在靠近根节点的位置使节点的带权路径长度最小。
  • 具体步骤:
代码语言:txt
复制
1、根据给定的n个权值{w1,w2,…,wn}构成n棵二叉树的集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi根结点,其左右子树均为空。
2、在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。
3、在F中删除这两棵树,同时将新得到的二叉树加入F中。
4、重复2和3步骤,直到F只含一棵树为止。这棵树便是哈夫曼树。

图解

  • Java 实现
代码语言:java
复制
/**
 * 哈夫曼树
 *
 */
public class HuffmanTree {
    public static void main(String[] args) {
        HashMap<String, Integer> hashMap = new HashMap<>(8);
        hashMap.put("A", 5);
        hashMap.put("B", 87);
        hashMap.put("C", 4);
        hashMap.put("D", 2);
        hashMap.put("E", 2);
        hashMap.put("F", 9);

        ArrayList<HuffmanTreeNode> huffmanTreeNodes = new ArrayList<>();
        for (String key : hashMap.keySet()) {
            HuffmanTreeNode huffmanTreeNode = new HuffmanTreeNode(key, hashMap.get(key));
            huffmanTreeNodes.add(huffmanTreeNode);
        }

        HuffmanTreeNode root = createTree(huffmanTreeNodes);

        printTree(root);
    }

    /**
     * 先序遍历哈夫曼树 根左右
     *
     * @param root
     */
    private static void printTree(HuffmanTreeNode root) {
        if (root == null) {
            return;
        }
        System.out.println("key:" + root.key + " weight:" + root.weight);
        printTree(root.leftChildren);
        printTree(root.rightChildren);
    }

    /**
     * 创建哈夫曼树
     *
     * @param huffmanTreeNodes
     * @return
     */
    private static HuffmanTreeNode createTree(ArrayList<HuffmanTreeNode> huffmanTreeNodes) {
        HuffmanTreeNode huffmanTreeNodeNew = null;
        while (huffmanTreeNodes.size() >= 2) {
            // 取出权值最小的两个值
            huffmanTreeNodes.sort(Comparator.comparing(o -> o.weight));
            String newKey = huffmanTreeNodes.get(0).key + huffmanTreeNodes.get(1).key;
            int newWeight = huffmanTreeNodes.get(0).weight + huffmanTreeNodes.get(1).weight;
            System.out.println(newKey + newWeight);
            huffmanTreeNodeNew = new HuffmanTreeNode(newKey, newWeight);
            huffmanTreeNodeNew.leftChildren = huffmanTreeNodes.get(0);
            huffmanTreeNodeNew.rightChildren = huffmanTreeNodes.get(1);
            huffmanTreeNodes.remove(0);
            huffmanTreeNodes.remove(1);
            huffmanTreeNodes.add(huffmanTreeNodeNew);
        }
        return huffmanTreeNodeNew;
    }

    /**
     * 哈夫曼树节点
     */
    static class HuffmanTreeNode {
        String key;
        // 权重
        Integer weight;

        HuffmanTreeNode leftChildren;

        HuffmanTreeNode rightChildren;

        public HuffmanTreeNode(String key, Integer weight) {
            this.key = key;
            this.weight = weight;
        }
    }
}

应用-哈夫曼编码

  • 哈夫曼编码在数据压缩中有非常广泛的运用。
  • 哈夫曼编码是可变长编码(VLC)的一种。如果长短不等其实很容易混淆,若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码又称做前缀编码。

案例

  • 比如我们有一段文字内容为“BADCADFEED”要网络传输给别人,使用定长编码(3位)用二进制的数字(0和1)来实现,我们可以用相应的二进制数据编码表示:
  • 使用上述编码进行编码对文字内容“BADCADFEED”,真正传输的数据就是编码后的“001 000 011 010 000 011 101 100 100 011”,对方接收时可以按照3位一分来译码。如果一篇文章很长,这样的二进制串也将非常的可怕。

哈夫曼编码构建

  • 实际上,一段内容中不同的字符出现的频率是不同的,哈夫曼树编码的的思想就是使出现频率高的字符编码长度尽可能小。
  • 上述字符串“BADCADFEED”构建哈夫曼编码,流程如下图所示:
  • 定长编码二进制串:001 000 011 010 000 011 101 100 100 011(共30个字符)
  • 哈夫曼编码二进制串:100 000 01 101 000 01 001 11 11 01(共25个字符)
  • 可以看到编码数据被压缩节约了大约17%的存储或传输成本。随着字符的增加和多字符权重的不同,这种压缩会更加显出其优势。

总结

  • 给定N个权值作为N个叶子节点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。最优二叉树经常用于数据存储和传输中来压缩数据,减少存储成本和传输成本。
  • 构造最优二叉树的过程中,子树的构建可能有多种选择,因此构建的最优二叉树也可能不同,但树的带权路径长度一定满足等于最小值。

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 哈夫曼树(最优二叉树)
    • 哈夫曼算法
      • 图解
        • 应用-哈夫曼编码
          • 案例
          • 哈夫曼编码构建
      • 总结
      • 个人简介
      相关产品与服务
      数据保险箱
      数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档