前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >哈夫曼树 编码-哈夫曼树原理及Java编码实现

哈夫曼树 编码-哈夫曼树原理及Java编码实现

作者头像
宜轩
发布于 2022-12-29 01:14:48
发布于 2022-12-29 01:14:48
54400
代码可运行
举报
文章被收录于专栏:囍楽云博客囍楽云博客
运行总次数:0
代码可运行

文章目录前言

  所有博客文件目录索引:​​博客目录索引(持续更新)​​

  源代码:​​Gitee—.java​​​、​​Github—.java​​

  一、哈夫曼树原理

  对于哈夫曼树的构造以及权值计算原理知识点推荐看这个视频:​​哈夫曼树和哈夫曼编码—​​

  哈夫曼编码有两个特点:

  带权路径长度WPL最短且唯一;【核心减少编码的操作】编码互不为前缀(一个编码不是另一个编码的开头)【可进行还原用途】。

  应用场景:压缩文件。

  公式:路径长度:WPL = l1× w1+l2× w2 +…+ ln × wn,w表示权值,n表示叶子节点个数。

  哈夫曼编码是如何进行应用的呢,有什么具体的示例呢?

  哈夫曼树是一颗二叉树哈夫曼树 编码,其是根据元素的权重来进行构成的一棵树,在树上的每个节点val都使用0或1来进行表示。

  就像下面一样,可以说每个元素值所对应的路径都是唯一的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
300000    500001    110001    前者是值,后者即为路径,就是从上到下的路径合并。

  问题来了为什么要构成构造成一个哈夫曼树?尤其是为什么要根据权重来进行排列分布呢?

  首先解决后面一个问题:若是在一组数据中,有10个不同的字符,字符a出现了1000次,其他字符出现的数量很少,我们是否可以根据出现的次数来对这些字符来进行编码,a用1表示,b字符用01…d字符使用0003表示,那么1000次a仅仅只需要1000个字符即可哈夫曼树 编码,是不是大大减少了存储空间?这也就是我们为什么要根据权重进行排列分布。

  此时可以来说明前面一个问题:因为哈夫曼树是带权路径长度最短的树,权值较大的节点离根节点较近。而带权路径长度是指:树中所有的叶子节点的权值乘上其到根节点的路径长度,这与最终的哈夫曼编码总长度成正比关系的。

  核心操作:一旦哈夫曼树构建出来之后,我们可以得到每个字符与其路径,那么我们根据这个hash表即可进行字符串编码,而由于每个路径都是唯一的,我们同样也可依靠hash表来进行解码!

  二、哈夫曼编码(Java题解)

  编码思路过程:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
encode编码:构造哈夫曼树 -> 获取字符及路径map  ->  根据map去构建指定编码      1、构造哈夫曼树:        准备条件:自定义树节点(字符、val、权重,其中val为之后路径组成的值)        过程:①准备一个map来统计所有字符出现次数即权重。②根据权重进行排序。③遍历map来构建树节点,接着根据权重排序,存储到一个链表节点中。④遍历链表节点每次取出两个构成一个虚拟节点,之后再找前缀最小的进行重复合并,视角是从底之上进行合并构建。      2、获取字符及路径map:递归过程,核心路径重点就是叶子节点,在哈夫曼树里有效字符节点都是叶子节点。      3、根据map中的字符与对应匹配的路径来进行对所有源字符遍历编码。        decode解码:根据map来进行前缀匹配(由于每个字符的路径唯一),即可进行还原字符

  Java题解:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/*
* 提示:该行代码过长,系统自动注释不进行高亮。一键复制会移除系统注释 
* package com.changlu.tree;        import java.util.Collections;    import java.util.HashMap;    import java.util.LinkedList;    import java.util.Map;        /      @Description: 哈夫曼树      @Author: changlu      @Date: 8:09 PM     /    //哈夫曼树节点    class TreeNode {        public Character ch;        public int val;        public int freq;        public TreeNode left;        public TreeNode right;        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;        }    }        public class Huffman {            /          解码          @param encodeStr 已编码字符串          @param encodeMap 编码map集合          @return         /        public static String decode (String encodeStr, Map encodeMap) {            StringBuilder decodeStr = new StringBuilder();            while (encodeStr.length() > 0) {                //遍历所有编码map对应的value(也就是编码路径)                for (Map.Entry entry : encodeMap.entrySet()) {                    String charEncode = entry.getValue();                    //匹配路径前缀                    if (encodeStr.startsWith(charEncode)) {                        decodeStr.append(entry.getKey());                        //删除该前缀                        encodeStr = encodeStr.substring(charEncode.length());                        break;                    }                }            }            return decodeStr.toString();        }            //编码        //0:编码后的字符串        //1:对应哈弗曼树中字符的路径表示        public static Object[] encode(String s) {            Object[] res = new Object[2];            //编码字符串            //1、构建哈夫曼树            TreeNode rootNode = constructTree(s);            //2、根据哈夫曼树找到所有的路径并存储到map中            Map encodeMap = new HashMap();            findPath(rootNode, encodeMap, new StringBuilder());//存储所有字符、编码路径到map中            //此时 字符:路径编码  已经再encodeMap中存储了,我们即可来进行编码            StringBuilder sb = new StringBuilder();            for (int i = 0; i < s.length(); i++) {                sb.append(encodeMap.get(s.charAt(i)));            }            res[0] = sb.toString();            res[1] = encodeMap;            return res;        }            /          寻找到哈夫曼树中所有字符的路径,并存储到map中          @param root 哈夫曼树的根节点          @param encodeMap 编码map          @param path 存储路径         /        private static void findPath(TreeNode root, Map encodeMap, StringBuilder path) {            //每个元素的终点就是n0节点            if (root.left == null || root.right == null) {                path.append(root.val);                //为什么要从第1位开始呢?因为完全可以再少一位0,这样也能减少不少字符                encodeMap.put(root.ch, path.substring(1));                path.deleteCharAt(path.length() - 1);                return;            }            path.append(root.val);            //递归左右            if (root.left != null) findPath(root.left, encodeMap, path);            if (root.right != null) findPath(root.right, encodeMap, path);            //删除所有最后一个字符            path.deleteCharAt(path.length() - 1);        }                /          构造哈夫曼树          @param s          @return         /        private static TreeNode constructTree(String s) {            //1、统计每个字符出现的权重            Map cntMap = new HashMap();            for (int i = 0; i < s.length(); i++) {                cntMap.put(s.charAt(i), cntMap.getOrDefault(s.charAt(i), 1));            }            //2、将所有的字符构建成TreeNode节点存储到LinkedList中            LinkedList nodelist = new LinkedList();            for (Map.Entry entry : cntMap.entrySet()) {                Character ch = entry.getKey();//字符                Integer freq = entry.getValue();//频率                int val = 0;//节点值                nodelist.add(new TreeNode(ch, val, freq, null, null));            }            //3、根据频率来进行排序            Collections.sort(nodelist, (node1, node2)->node1.freq - node2.freq);            //4、构建哈夫曼树            //处理只有一个节点的情况            if (nodelist.size() == 1) {                //1个节点也要有父节点                TreeNode treeNode = nodelist.get(0);                return new TreeNode(null, 0, treeNode.freq, treeNode, null);            }            //开始构建哈夫曼树            TreeNode root = null;            while (nodelist.size() > 0) {                //取出头部两个节点                TreeNode t1 = nodelist.removeFirst();                TreeNode t2 = nodelist.removeFirst();                //左右子树节点来进行分别赋值0,1                t1.val = 0;                t2.val = 1;                //若是当前节点中没有节点了                if (nodelist.size() == 0) {                    root = new TreeNode(null, 0, t1.freq + t2.freq, t1, t2);                }else {                    //首先构建成一个虚拟节点                    TreeNode combineNode = new TreeNode(null, 0, t1.freq + t2.freq, t1, t2);                    //将虚拟节点插入到链表中,同样需要维护其有序性                    //暴力法【实际这里可用二分来进行】                    if (combineNode.freq > nodelist.getLast().freq) {                        nodelist.addLast(combineNode);                    }else {                        //遍历找到权重>该合并节点的权重                        for (int i = 0; i < nodelist.size(); i++) {                            if (nodelist.get(i).freq >= combineNode.freq) {                                nodelist.add(i, combineNode);                                break;                            }                        }                    }                }            }            return root;        }        }
*/

  测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Huffman {        public static void main(String[] args) {            String s = "abbbbeeerrryuiolll";            System.out.println("编码前:" + s);            //编码API            Object[] encodeRes = encode(s);            String encodeStr = (String) encodeRes[0];            Map encodeMap = (Map) encodeRes[1];            System.out.println("编码表:");            for (Map.Entry e : encodeMap.entrySet()) {                System.out.println(e.getKey() + ":" + e.getValue());            }            System.out.println("编码后:" + encodeStr);            //解码API            String decodeStr = decode(encodeStr, encodeMap);            System.out.println("解码后:" + decodeStr);        }    }

  参考资料

  1 ​​哈夫曼编码( Coding)原理详解​​

  2. ​​哈夫曼编码细解& Java 实现​​

  3. ​​视频:哈夫曼树和哈夫曼编码​​

  4. ​​【JAVA】KMP算法保姆级教程​​

本文共 1346 个字数,平均阅读时长 ≈ 4分钟

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
听说最近HTML5很火~~!---贪吃蛇小游戏
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>贪吃蛇</title> <style type="text/css"> *{margin:0;padding: 0;font-family: "Microsoft YaHei";} #page{margin-right: auto;margin-left: auto; margin-top: 20px;height: 600px; width:
赵腰静
2018/03/09
8370
贪吃蛇的使命 | 零基础入门贪吃蛇游戏(附源码、演示地址)
大家好啊,老铁们,二零二零年八月二十九日,一个人来到成都的第六天。人生总有许许多多的不如意。每天都会遇见不同的人,经历不同的事,还好我们年轻,经得起折腾!
C you again
2020/09/11
7640
贪吃蛇的使命 | 零基础入门贪吃蛇游戏(附源码、演示地址)
使用宝塔面板搭建网站服务,并实现公网远程访问「内网穿透」
宝塔面板作为简单好用的服务器运维管理面板,它支持Linux/Windows系统,我们可用它来一键配置LAMP/LNMP环境、网站、数据库、FTP等,通过Web端轻松管理服务器。
iOS Magician
2023/10/11
2.8K0
使用宝塔面板搭建网站服务,并实现公网远程访问「内网穿透」
canvas画出时钟
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .clock { width: 400px; margin: 100px auto; background: #ddd; border-radius: 20px;
IT工作者
2022/02/08
3960
100行代码,使用 Pygame 制作一个贪吃蛇小游戏!
相信我们大家都玩过贪吃蛇游戏,今天我们就从头一起来写一个贪吃蛇小游戏,只需要100多行的代码就完成了
周萝卜
2021/10/13
4K1
程序员是怎么玩贪吃蛇的?
前言 看别人玩贪吃蛇永远牛逼,自己玩永远菜鸡... http://mpvideo.qpic.cn/0bf224aayaaaoiah2vnkefqfbv6dbtlqadaa.f10002.mp4
DeROy
2021/06/01
5430
程序员是怎么玩贪吃蛇的?
Canvas
http://www.w3c.org/TR/2dcontext/ https://html.spec.whatwg.org/
jinghong
2020/05/09
13.2K0
Canvas
AS3:小游戏“贪吃蛇”的实现
前几天在园子里看到有人用Silverlight做了一个"贪吃蛇",一时兴起也想用AS3.0做一个,虽然这个游戏已经被很多开发者做烂了,但是作为AS的初学者,重新做一遍也当是一种学习. 技术"难"点分析: 1.蛇身的构成 可以用数组来存储一堆小球,将它们排列成连续的直线即可 2.蛇身的移动 蛇头移动后,紧跟蛇头后的小球移动到蛇头原来的位置,然后...类推,后面的小球依次移动到前一个球的位置 3.碰撞检测 蛇头移动时,如果超出舞台边界,则Game Over了;同样蛇头如果遇到了蛇身,同样也Over. 4.食物
菩提树下的杨过
2018/01/22
9881
Canvas动画展示(番外)
canvas动画:   1.清空canvas   在绘制每一帧动画之前,需要清空所有。清空所有最简单的做法就是clearRect()方法   2.保存canvas状态   如果在绘制的过程中会更改canvas的状态(颜色、移动了坐标原点等),又在绘制每一帧时都是原始状态的话,则最好保存下canvas的状态   3.绘制动画图形   从这里开始一帧一帧的绘制动画   4.恢复canvas状态   如果你前面保存了canvas状态,则应该在绘制完成一帧之后恢复canvas状态   5.控制动画   我们可用通过canvas的方法或者自定义的方法把图像会知道到canvas上。正常情况,我们能看到绘制的结果是在脚本执行结束之后。 例如:我们不可能在一个 for 循环内部完成动画。为了执行动画,我们需要一些可以定时执行重绘的方法。
我不是费圆
2020/12/17
6840
canvas荧光表源码分享
无聊看了一下网页制作从入门到放弃的书,看到一个canvas表盘案例觉得不错,就搞出来美化了一下,就是这个鸟样子 <!DOCTYPE html> <html> <head> <meta charse
Youngxj
2018/06/07
6620
C++ 纯 C 实现贪吃蛇小游戏
一枚大果壳
2024/05/18
3810
C++ 纯 C 实现贪吃蛇小游戏
贪吃蛇C语言代码
虽然说是智能但是可能并没有你想象中那么智能==。 基本思路是按照上、右、下、左的顺序搜索方向,使得沿该方向前进能够靠近食物,前进过程中遇到障碍会自动绕开,可是不能避免蛇头被蛇身包围的情况。
全栈程序员站长
2022/09/01
6.5K0
Canvas监测雷达
已经很晚了,祝愿大家做个好梦。如果你也如我一般,对Canvas 或者 Css 有着独有的情愫,加入我,让手中的代码变得生机勃勃,我是 “ 我不是费圆 ”,一个正在努力的前端程序员。
我不是费圆
2020/10/09
9360
自定义页尾代码
自定义页尾代码 效果图 <!--时钟--> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title></title> </head> <body> <div style="text-align: center; color: mediumvioletred;">
codeniu
2022/02/25
5330
自定义页尾代码
H5 Canvas 旋转小伞+时钟
原生态的js, 利用H5 Canvas 写的旋转小伞+时钟 指针和表盘会变颜色哦!指针到达小伞位置,会跟四周的小伞颜色一致! H5 Canvas 旋转小伞+时钟 效果如下: JavaScript代码: <script type="text/javascript" language="javascript"> window.onload=function () { // 创建画布 var canvas=document.createElement('canvas'); var brus
AlexTao
2019/08/17
1.1K0
使用Nodejs搭建HTTP服务,并实现公网远程访问「内网穿透」
Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation(原为 Node.js Foundation,已与 JS Foundation 合并)持有和维护,亦为 Linux 基金会的项目。Node.js 采用 Google 开发的 V8 运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于资料密集的即时应用程序。
iOS Magician
2023/05/02
1.4K0
使用Nodejs搭建HTTP服务,并实现公网远程访问「内网穿透」
canvas绘制坐标系
第六步:用for循环绘制表格。 问题?怎么绘制? 第一步:每一次的循环都开启一个新的路径。根据xy坐标绘制就行了.(默认canvas左上角开始). 为什么-0.5,因为默认情况下线条的中心点和像素的底部对齐所以会2显示,所以显示非纯黑色问题。所以-0.5,代表0.52=1
贵哥的编程之路
2021/03/18
5740
canvas 系列学习笔记三《样式和颜色》
通过上下文可以设置strokeStyle (线条颜色) 和 fillStyle (填充颜色)。 设置完之后画线和填充就是设置的颜色。
星宇大前端
2022/09/28
6050
canvas 系列学习笔记三《样式和颜色》
【项目实战】Java 贪吃蛇
在主启动类StartGame中添加frame.add(new GamePanel());,
sidiot
2023/08/31
2650
【项目实战】Java 贪吃蛇
用原生JavaScript写一个贪吃蛇
看到掘金上有这样一种效果,感觉很好看,就是那种毛玻璃效果,于是想试试写一个登录页面并且实现遮罩,但是写成了开始游戏,可是光一个开始游戏也没意思,干脆写一个小游戏吧,直接试试贪吃蛇。
JanYork_简昀
2022/06/06
8280
用原生JavaScript写一个贪吃蛇
推荐阅读
相关推荐
听说最近HTML5很火~~!---贪吃蛇小游戏
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档