我们经常会用到文件压缩,压缩之后文件会变小,便于传输,使用的时候又将其解压出来。为什么压缩之后会变小,而且压缩和解压也不会出错。赫夫曼编码和赫夫曼树了解一下。

赫夫曼树:

它是一种的叶子结点带有权重的特殊二叉树,也叫最优二叉树。既然出现最优两个字肯定就不是随便一个叶子结点带有权重的二叉树都叫做赫夫曼树了。

赫夫曼树中有一个很重要的概念就是带权路径,带权路径最小的才是赫夫曼树。

树的路径长度是从根结点到每一个结点的长度之和,带权路径就是每一个结点的长度都乘以自己权重,记做WPL。

假设有abcd数据,权重分别是7 5 2 4。下面构建出来的三棵带权二叉树。

A树:WPL=7*2+5*2+2*2+4*2=36

B树:WPL=7*3+5*3+2*1+4*2=46

C树:WPL=7*1+5*2+2*3+4*3=35

显然C树的带权是最小的。而且无构建出比它更小的了。所以C树就是赫夫曼树

我们从C树发现了一个问题,就是要使得树的带权路径最小,那么权重越大的就应该离根结点越近。所以如果要构建一棵赫夫曼树,首先一定要将数据按权重排序。这是不是就是之前提到的贪心算法,一定有排序,从局部最优到整体最优。

赫夫曼编码:

我们都知道以前的地下党发送电报。都是加密了发送,然后使用密码本来解密。

我们还是发送上面的abcd

显然计算机的世界都是0和1,假设我们用三位来表示上面的字符。也就相当于制作一个密码本

a:000

b:001

c:010

d:011

那么我要传输的就变成了000001010011,然后收到之后按照三位一分来解密就可以了。但是如果数据很多之后。我们可能就不能不用3位来表示了,可能是8位,10位之类了的,那么这个二进制串的长度也相当可怕了。

再看赫夫曼树,如果我们将上面的C图的每一个左分支表示0,右分支表示1

那么现在表示abcd就可以用每个结点长度路径上的值来表示了

a:0

b:10

c:110

d:111

abcd就可以表示为010110111,就从刚才的000001010011的12位缩减到了9位,如果数据量大,这个减少的位数是很可观的。

但是又有一个问题了,这样出来的编码长度不等,其实很容易混淆,所以要设计这种长短不等的编码,必须任意字符的编码都不是另一个字符编码的前缀,这种编码称做前缀编码。显然通过二叉树这样构造出来的编码,每个叶子结点都不同的编码。而这棵赫夫曼树就是我们的密码本。也就是说编码于解码都需要用同样结构的赫夫曼树。

解码:

每次从根开始寻找,找到叶子结点为止,然后又从根开始寻找,比如010110111,

0走左边,左边第一个就是叶子结点,所以找到a,

回到根继续寻找,编码串还剩下10110111,

1走右边,0走左边找到b,110 ->c, 111->d

一般来说设要编码的字符集{c1,c2,c3...},设置各个字符出现的频率{w1,w2,w3...},以各字符作为叶子结点,以相应的频率作为权重来构造赫夫曼树。

赫夫曼树的构建

以我们上面的a:7 b:5 c:4 d:2为例。

1.上面从树的特点来看,首先我们需要按照权重从小到大排序,注意赫夫曼树的构建是逆向构建的,就是说是从叶子结点往根结点构建。排序:d:2  c:4  b:5  a:7

2.取前面两个权值最小结点作为新结点n1的两个子结点,注意二叉树的左小右大规则。新结点的权重为两孩子权重之和,将操作过的结点从数据中移除,新结点放进去继续操作:

n1的权重是 cd权重之和为6,新的排序:b:5  n1:6  a:7

3.取出b和n1构成新作为新结点n2的两个子结点剩余。 新的排序:a:7  n2:11

直到操作到最后两个结点结束。

如果遇到操作的两个结点在已有的数上面还没有,那就另开一个子树,等到操作这个新子树的根结点的时候,再把这棵子树直接移植过去,比如这个数据来构建a:3 b:24 c:6 d:20 e:34 f:4 g:12

排序:a:3  f:4  c:6  g:12  d:20  b:24  e:34

d:20 和b:24 构造出来的子树就是后面移植上去的

代码实现:

现在就按照上面的逻辑,代码实现赫夫曼树的构建和编码解码,对比上面的第二个数据验证结果

package com.nijunyang.algorithm.tree;

import java.util.*;

/**
* Description: 哈夫曼树
* Created by nijunyang on 2020/4/28 21:43
*/
public class HuffmanTree { private static final byte ZERO = 0;
private static final byte ONE = 1;
HuffmanNode root;
Map<Character, Integer> weightMap; //字符对应的权重
List<HuffmanNode> leavesList; // 叶子
Map<Character, String> leavesCodeMap; // 叶子结点的编码 public HuffmanTree(Map<Character, Integer> weightMap) {
this.weightMap = weightMap;
this.leavesList = new ArrayList<>(weightMap.size());
this.leavesCodeMap = new HashMap<>(weightMap.size());
creatTree();
} public static void main(String[] args) {
Map<Character, Integer> weightMap = new HashMap<>();
//a:3 f:4 c:6 g:12 d:20 b:24 e:34
weightMap.put('a', 3);
weightMap.put('b', 24);
weightMap.put('c', 6);
weightMap.put('d', 20);
weightMap.put('e', 34);
weightMap.put('f', 4);
weightMap.put('g', 12);
HuffmanTree huffmanTree = new HuffmanTree(weightMap);
//abcd: 1011001101000
String code = huffmanTree.encode("abcd");
System.out.println(code);
System.out.println("1011001101000".equals(code));
String msg = huffmanTree.decode(code);
System.out.println(msg); } /**
* 构造树结构
*/
private void creatTree() {
PriorityQueue<HuffmanNode> priorityQueue = new PriorityQueue<>();
weightMap.forEach((k,v) -> {
HuffmanNode huffmanNode = new HuffmanNode(k, v);
priorityQueue.add(huffmanNode);
leavesList.add(huffmanNode);
});
int len = priorityQueue.size();//先把长度取出来,因为等下取数据队列长度会变化 //HuffmanNode实现了Comparable接口,优先队列会帮我们排序,我们只需要每次弹出两个元素就可以了
for (int i = 0; i < len - 1; i++) {
HuffmanNode huffmanNode1 = priorityQueue.poll();
HuffmanNode huffmanNode2 = priorityQueue.poll();
int weight12 = huffmanNode1.weight + huffmanNode2.weight; HuffmanNode parent12 = new HuffmanNode(null, weight12); //父结点不需要数据直接传个null
parent12.left = huffmanNode1; //建立父子关系,因为排好序的,所以1肯定是在左边,2肯定是右边
parent12.right = huffmanNode2;
huffmanNode1.parent = parent12;
huffmanNode2.parent = parent12;
priorityQueue.add(parent12); //父结点入队
}
root = priorityQueue.poll(); //队列里面的最后一个即是我们的根结点 /**
* 遍历叶子结点获取叶子结点数据对应编码存放起来,编码时候直接拿出来用
*/
leavesList.forEach(e -> {
HuffmanNode current = e;
StringBuilder code = new StringBuilder();
do {
if (current.parent != null && current == current.parent.left) { // 说明当前点是左边
code.append(ZERO); //左边0
} else {
code.append(ONE);//左边1
}
current = current.parent;
}while (current.parent != null); //父结点null是根结点
code.reverse(); //因为我们是从叶子找回去的 ,所以最后需要将编码反转下
leavesCodeMap.put(e.data, code.toString());
});
} /**
* 编码
*/
public String encode(String msg) {
char[] chars = msg.toCharArray();
StringBuilder code = new StringBuilder();
for (int i = 0; i < chars.length; i++) {
code.append(leavesCodeMap.get(chars[i]));
}
return code.toString();
} /**
* 解码
*/
public String decode(String code) {
char[] chars = code.toCharArray();
Queue<Byte> queue = new ArrayDeque();
for (int i = 0; i < chars.length; i++) {
queue.add(Byte.parseByte(String.valueOf(chars[i])));
}
HuffmanNode current = root;
StringBuilder sb = new StringBuilder();
while (!queue.isEmpty() ){
Byte aByte = queue.poll();
if (aByte == ZERO) {
current = current.left;
}
if (aByte == ONE) {
current = current.right;
}
if (current.right == null && current.left == null) {
sb.append(current.data);
current = root;
}
}
return sb.toString();
} /**
* 结点 实现Comparable接口 方便使用优先队列(PriorityQueue)排序
*/
private class HuffmanNode implements Comparable<HuffmanNode>{ Character data; //字符
int weight; //权重
HuffmanNode left;
HuffmanNode right;
HuffmanNode parent; @Override
public int compareTo(HuffmanNode o) {
return this.weight - o.weight;
}
public HuffmanNode(Character data, int weight) {
this.data = data;
this.weight = weight;
}
}
}

高级数据结构---赫(哈)夫曼树及java代码实现的更多相关文章

  1. hdu 2527:Safe Or Unsafe(数据结构,哈夫曼树,求WPL)

    Safe Or Unsafe Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  2. C语言数据结构之哈夫曼树及哈夫曼编码的实现

    代码清单如下: #pragma once #include<stdio.h> #include"stdlib.h" #include <string.h> ...

  3. 【数据结构】赫夫曼树的实现和模拟压缩(C++)

    赫夫曼(Huffman)树,由发明它的人物命名,又称最优树,是一类带权路径最短的二叉树,主要用于数据压缩传输. 赫夫曼树的构造过程相对比较简单,要理解赫夫曼数,要先了解赫夫曼编码. 对一组出现频率不同 ...

  4. 哈夫曼树(三)之 Java详解

    前面分别通过C和C++实现了哈夫曼树,本章给出哈夫曼树的java版本. 目录 1. 哈夫曼树的介绍 2. 哈夫曼树的图文解析 3. 哈夫曼树的基本操作 4. 哈夫曼树的完整源码 转载请注明出处:htt ...

  5. Java 树结构实际应用 二(哈夫曼树和哈夫曼编码)

     赫夫曼树 1 基本介绍 1) 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为 最优二叉树,也称为哈夫曼树(Huffman Tree), ...

  6. Android版数据结构与算法(七):赫夫曼树

    版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...

  7. Java数据结构和算法(四)赫夫曼树

    Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...

  8. javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

    赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...

  9. C#数据结构-赫夫曼树

    什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小.哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点 ...

随机推荐

  1. [HDU1029]Ignatius and the Princess IV<桶 水题>

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1029 题目大意: 多组数据,每组数据先给一个n,然后给n各数字,找出n各数字中出现了至少(n+1)/2 ...

  2. Python执行js之PyexecJs

    利用Python执行js 爬虫中会经常碰到JS加密,当我们找到他加密的JS代码之后我们需要获取它的返回值,python虽然可以模仿js写一个python版本的加密,但是这样有点费时间,也有点费头发~ ...

  3. 3.26java作业

    1.编写程序, 输入变量x的值,如果是1,输出x=1,如果是5,输出x=5,如果是 10,输出 x=10,除了以上几个值,都输出x=none.(知识点:if条件语句) package fda; imp ...

  4. 携程首页--使用flex布局实现

    携程首页 flex解决了float和postion的遗留问题,对移动端比较友好. 需要水平排列的元素就为其父元素设置display:flex,并为子元素添加flex的值(比例) 布局时可以先从大的页面 ...

  5. django自定义404和500页面

    from django.contrib import admin from django.urls import path urlpatterns = [ path('admin/', admin.s ...

  6. go 基本包

    像 fmt.os 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分(一些底层的除外)内置于 Go 本身 unsafe: 包含了一些打破 Go 语言“类型安全”的命 ...

  7. Linux管理,例行性工作,at和cron

    at 仅执行一次就结束调度(服务atd必须启动) at的配置信息 1.我们设定的at进程其实都是被以文本的方式写入到/var/spool/at/目录内,然后这些设定的进程便会等待atd服务来启动这些进 ...

  8. Google 浏览器 离线包下载方式

    最近因工作需要,需要安装google浏览器,并且安装在系统固定目录,用360软件管理下载后发现默认安装在C:\Users\administrator\AppData\Local\Google\Chro ...

  9. go 中的WaitGroup

    wait_group sync.WaitGroup 类型是并发安全的,也是开箱就能用的. 该类型有三个指针方法,即:Add,Done和Wait. sync.WaitGroup是一个结构体类型.其中一个 ...

  10. 【FreeMarker】【程序开发】数据模型,对象包装

    [FreeMarker][程序开发]数据模型,对象包装 分类: Java.FreeMarker2014-10-25 18:49 413人阅读 评论(0) 收藏 举报 FreeMarker   目录(? ...