算法面试题-美团点评2016研发工程师编程题(二)-字符编码(哈夫曼树)
题目:
解析:这个题目的关键问题是“最短的编码”,这里可以知道应该是Huffman编码了。
哈夫曼编码是一种可变字长编码,也就是说对于不同的字符的编码不是定长的,所以才能比定长编码要短。
哈夫曼树
哈夫曼编码依靠的就是哈夫曼树,根据每个字符出现的次数作为权重,生成对应的哈夫曼树,对应的编码长度即为最短。
哈夫曼树的构造很简单,每次从所有的权重中选出最小两个分别作为的作为子节点(一般左节点权重小于右节点),权重相加值为父节点权重,再把父节点代替两个子节点重新进入队列中,不断循环最后会得到一颗二叉树。
例子:以2,4,5,8,13为权构造哈夫曼树
① 选出最小两个数为2和4,以2为左节点,4为右节点构造二叉树,和为父节点的值,并且将父节点的权放回
完成后权值为:5,6,8,13
② 选出最小两个数5和6,操作同上
权值为:8,11,13
③ 选出8和11,操作同上
权值为:13,19
④ 最后剩下两个,分别作为左右节点
得到的二叉树就是哈夫曼树。
回到哈夫曼编码,哈夫曼编码对应的就是将每个字符出现的次数作为权,生成对应的哈夫曼树,然后以左子树为0,右子树为1,对每个字符进行编码。
所以对于题目中的例子,生成的哈夫曼树和对应的编码应该是这样的:
注意:二叉树的构造不一定相同,因为部子节点的权是相同的,构造的二叉树会有所不同。
代码:
① 定义哈夫曼树节点:
1 public class Node { 2 Character value; // 节点字符 3 int count = 1; // 深度(节点与根节点的距离) 4 int power = 1; // 权(出现次数) 5 Node left = null; // 左子节点 6 Node right = null; // 右子节点 7 8 /** 9 * 更新子节点的深度 10 */ 11 public void notifyChild() { 12 count += 1; 13 if (left != null) { 14 left.notifyChild(); 15 } 16 if (right != null) { 17 right.notifyChild(); 18 } 19 } 20 21 public Node(Character value) { 22 this.value = value; 23 } 24 25 public boolean sameValue(char c) { 26 return c == value; 27 } 28 29 /** 30 * 根据传入的节点作为此节点的子节点 31 * @param left 左节点 32 * @param right 右节点 33 */ 34 public void updateChild(Node left, Node right) { 35 this.left = left; 36 this.right = right; 37 this.power = left.power + right.power; 38 } 39 40 }
11-19行为更新操作,因为最后要得到每个字符的编码长度,这个长度实际上是在构造二叉树的时候可以确定的,因为如果父节点被选出,则其子节点的深度会增加1,这个方法用来通知子节点进行深度加一操作。
② 编写计算方法
1 void count(String s){ 2 // 计算每个字符出现的次数 3 ArrayList<Node> chars = new ArrayList<>(); 4 char[] StrChars = s.toCharArray(); 5 chars.add(new Node(StrChars[0])); 6 for (int j = 1; j < StrChars.length; j++) { 7 char c = StrChars[j]; 8 int size = chars.size(); 9 for (int i = 0; i < size; i++) { 10 if (chars.get(i).sameValue(c)) { 11 chars.get(i).power += 1; 12 break; 13 } 14 if (i == size - 1) { 15 chars.add(new Node(c)); 16 } 17 } 18 } 19 20 // 根据权值排序,从小到大,插入排序 21 ArrayList<Node> nodes = new ArrayList<>(); 22 nodes.add(chars.get(0)); 23 for (int i = 1; i < chars.size(); i++) { 24 int size = nodes.size(); 25 for (int j = 0; j < size; j++) { 26 Node c = chars.get(i); 27 if (c.power < nodes.get(j).power) { 28 nodes.add(j, c); 29 break; 30 } 31 if (j == size - 1) { 32 nodes.add(c); 33 } 34 } 35 } 36 chars = nodes; 37 38 // 构造哈夫曼树 39 while (chars.size() > 1) { 40 Node root = new Node(null); 41 Node left = chars.get(0); 42 Node right = chars.get(1); 43 root.updateChild(left, right); // 生成新节点 44 chars.remove(0); // 删除被选节点 45 chars.remove(0); // 删除被选节点 46 int size = chars.size(); 47 if (size == 0) { 48 chars.add(root); // 退出循环条件 49 } 50 for (int i = 0; i < size; i++) { 51 if (root.power < chars.get(i).power) { 52 chars.add(i, root); // 将新生成的节点插入到对应位置使得序列依然有序 53 root.notifyChild(); // 通知子节点更新深度 54 break; 55 } 56 if (i == size - 1) { 57 chars.add(root); 58 root.notifyChild(); 59 } 60 } 61 } 62 63 // 遍历二叉树,计算最短编码长度 64 int sum = 0; 65 Stack<Node> stack = new Stack<>(); 66 Node root = chars.get(0); 67 stack.add(root); 68 while (!stack.isEmpty()) { 69 root = stack.pop(); 70 if (root.value != null) { 71 sum += root.power * root.count; 72 } 73 if (root.right != null) { 74 stack.push(root.right); 75 } 76 if (root.left != null) { 77 stack.push(root.left); 78 } 79 } 80 System.out.println(sum); 81 }
注意:
① 这里排序使用了插入排序,时间复杂度较高,但是通过面试题是没问题的,考虑到sort方法不能用,就只能折中了。
② 这里的节点的深度是指子节点到根节点的距离,如例子中E到根节点距离为2
③ 深度的计算只有字符节点是正确的,因为方法中不加判断的对根节点和子节点都加一了,但是计算只要求子节点,所以忽略这个问题。
完整代码如下:
1 package com.fndroid; 2 3 import java.util.*; 4 5 public class Main { 6 7 public static void main(String[] args) { 8 Main solution = new Main(); 9 Scanner scanner = new Scanner(System.in); 10 while (scanner.hasNext()) { 11 solution.count(scanner.next()); 12 } 13 } 14 15 void count(String s){ 16 // 计算每个字符出现的次数 17 ArrayList<Node> chars = new ArrayList<>(); 18 char[] StrChars = s.toCharArray(); 19 chars.add(new Node(StrChars[0])); 20 for (int j = 1; j < StrChars.length; j++) { 21 char c = StrChars[j]; 22 int size = chars.size(); 23 for (int i = 0; i < size; i++) { 24 if (chars.get(i).sameValue(c)) { 25 chars.get(i).power += 1; 26 break; 27 } 28 if (i == size - 1) { 29 chars.add(new Node(c)); 30 } 31 } 32 } 33 34 // 根据权值排序,从小到大,插入排序 35 ArrayList<Node> nodes = new ArrayList<>(); 36 nodes.add(chars.get(0)); 37 for (int i = 1; i < chars.size(); i++) { 38 int size = nodes.size(); 39 for (int j = 0; j < size; j++) { 40 Node c = chars.get(i); 41 if (c.power < nodes.get(j).power) { 42 nodes.add(j, c); 43 break; 44 } 45 if (j == size - 1) { 46 nodes.add(c); 47 } 48 } 49 } 50 chars = nodes; 51 52 // 构造哈夫曼树 53 while (chars.size() > 1) { 54 Node root = new Node(null); 55 Node left = chars.get(0); 56 Node right = chars.get(1); 57 root.updateChild(left, right); // 生成新节点 58 chars.remove(0); // 删除被选节点 59 chars.remove(0); // 删除被选节点 60 int size = chars.size(); 61 if (size == 0) { 62 chars.add(root); // 退出循环条件 63 } 64 for (int i = 0; i < size; i++) { 65 if (root.power < chars.get(i).power) { 66 chars.add(i, root); // 将新生成的节点插入到对应位置使得序列依然有序 67 root.notifyChild(); // 通知子节点更新深度 68 break; 69 } 70 if (i == size - 1) { 71 chars.add(root); 72 root.notifyChild(); 73 } 74 } 75 } 76 77 // 遍历二叉树,计算最短编码长度 78 int sum = 0; 79 Stack<Node> stack = new Stack<>(); 80 Node root = chars.get(0); 81 stack.add(root); 82 while (!stack.isEmpty()) { 83 root = stack.pop(); 84 if (root.value != null) { 85 sum += root.power * root.count; 86 } 87 if (root.right != null) { 88 stack.push(root.right); 89 } 90 if (root.left != null) { 91 stack.push(root.left); 92 } 93 } 94 System.out.println(sum); 95 } 96 97 public class Node { 98 Character value; // 节点字符 99 int count = 1; // 深度(节点与根节点的距离) 100 int power = 1; // 权(出现次数) 101 Node left = null; // 左子节点 102 Node right = null; // 右子节点 103 104 /** 105 * 更新子节点的深度 106 */ 107 public void notifyChild() { 108 count += 1; 109 if (left != null) { 110 left.notifyChild(); 111 } 112 if (right != null) { 113 right.notifyChild(); 114 } 115 } 116 117 public Node(Character value) { 118 this.value = value; 119 } 120 121 public boolean sameValue(char c) { 122 return c == value; 123 } 124 125 /** 126 * 根据传入的节点作为此节点的子节点 127 * @param left 左节点 128 * @param right 右节点 129 */ 130 public void updateChild(Node left, Node right) { 131 this.left = left; 132 this.right = right; 133 this.power = left.power + right.power; 134 } 135 136 } 137 }
完整代码
转载于:https://www.cnblogs.com/Fndroid/p/6254187.html
算法面试题-美团点评2016研发工程师编程题(二)-字符编码(哈夫曼树)相关推荐
- 股票交易日(动态规划)----美团2016研发工程师编程题(二)
[编程题] 股票交易日 在股市的交易日中,假设最多可进行两次买卖(即买和卖的次数均小于等于2),规则是必须一笔成交后进行另一笔(即买-卖-买-卖的顺序进行).给出一天中的股票变化序列,请写一个程序计算 ...
- 网易2016研发工程师编程题--完全解析
前言 之前做公司的真题,碰到动态规划,还有一些数学性质的题目比较多一点.网易2016研发工程师编程题跟之前做的题目有很大的不同,不仅涉及到二叉树的编码,还涉及到图的广度遍历,最后还有一个快排.可以说这 ...
- 牛客网--蘑菇街2016研发工程师编程题
牛客网--蘑菇街2016研发工程师编程题 第一题: 搬圆桌 时间限制:1秒 空间限制:32768K 现在有一张半径为r的圆桌,其中心位于(x,y),现在他想把圆桌的中心移到(x1,y1).每次移动一步 ...
- 网易2016研发工程师编程题:扫描透镜
扫描透镜 在N*M的草地上,小明种了K个蘑菇,蘑菇爆炸的威力极大,小华不想贸然去闯,而且蘑菇是隐形的.只 有一种叫做扫描透镜的物品可以扫描出隐形的蘑菇,于是他回了一趟战争学院,买了2个扫描透镜,一个 ...
- 华为2016研发工程师编程题---删数
题目是这样的: 有一个数组a[N]顺序存放0~N-1,要求每隔两个数删掉一个数,到末尾时循环至开头继续进行,求最后一个被删掉的数的原始下标位置.以8个数(N=7)为例:{0,1,2,3,4,5,6,7 ...
- 蘑菇街2016研发工程师编程题--回文串
题目 给定一个字符串,问是否能通过添加一个字母将其变为回文串. 输入描述: 一行一个由小写字母构成的字符串,字符串长度小于等于10. 输出描述: 输出答案(YES\NO). 示例1 输入 coco 输 ...
- 网易2016研发工程师编程题 - 题解
题目链接: 第一部分,点这儿: 第二部分,点这儿. 第一题:小易的升级之路 题目: 小易经常沉迷于网络游戏.有一次,他在玩一个打怪升级的游戏,他的角色的初始能力值为 a.在接下来的一段时间内,他将会依 ...
- 血型遗传检测(pair原来没有先后顺序)----去哪儿2016研发工程师编程题
[编程题] 5-血型遗传检测 血型遗传对照表如下: 父母血型 子女会出现的血型 子女不会出现的血型 O与O O A,B,AB A与O A,O B,AB A与A A,O B,AB A与B A,B,AB, ...
- 比较重量 网易2016实习研发工程师编程题
题目: 小明陪小红去看钻石,他们从一堆钻石中随机抽取两颗并比较她们的重量.这些钻石的重量各不相同.在他们们比较了一段时间后,它们看中了两颗钻石g1和g2.现在请你根据之前比较的信息判断这两颗钻石的哪颗 ...
最新文章
- 基于EM参数估计的SAGE算法的MATLAB仿真
- vs2013编译boost1.55.0 32/64位
- 植入“电商基因” 传统产业搭上网络快车[图]
- Tool/IDE之MinGW:MinGW(C++环境)的简介、安装、使用方法之详细攻略
- 架构师成长系列 | 从 2019 到 2020,Apache Dubbo 年度回顾与总结
- SAP WebIDE里OData service catalog的实现原理
- 科目三路考需准备事项
- python邮箱爆破_Python在线爆破邮箱账号密码测试代码(亲测可用)
- linux进程状态d状态,linux – 进程永久停留在D状态
- 遍历目录下的所有文件-os.walk
- 那些年做的xmind思维导图
- 怎么接收xml报文_Benteler/本特勒 DESADV 报文生成过程
- 交换机命令中的正则表达式过滤方式
- React的调和过程(Reconcilliation)
- 一个div实现太极图案+动画(简单易懂)
- 计算机开机进桌面很久,教你解决win10电脑开机黑屏很久才进入桌面的方法
- 【JqGrid】jqgrid合并单元格
- 实现在网页上下载文件
- 佳肴 (Standard IO)
- 在jython中获取jython-[standalone-]x.x.x.jar执行文件目录
热门文章
- Java.lang.Boolean类
- js弹出窗体获得焦点
- 为什么我们如此迷恋眼科手术?
- 在package-lock.json中指定node-mass版本+独立编译flink中的flink-runtime-web模块
- Flink SQL Client实现CDC实验
- flink的dataset/stream/sql三套API的选择以及是否应该阅读源码
- spark-shell连接数据库java.sql.SQLSyntaxErrorException: Unknown databas
- 笔记本开机进入ubuntu16.04自动关闭触摸板
- 7.4.7 2DPCA
- 图像处理理论(八)——Meanshift, Camshift, Optical flow