【算法】赫夫曼树(Huffman)的构建和应用(编码、译码)
赫夫曼树的概念
二叉树结点的度
扩充二叉树


赫夫曼树的外结点和内结点
带权路径长度WPL


赫夫曼树(最优二叉树)




赫夫曼树的构建


Node类的设计
/**
* @Author: HuWan Peng
* @Date Created in 23:21 2018/1/14
*/
public class Node {
char data; // 数据
int weight; // 权值
int left, right, parent; // 三条链接
public Node (char data, int weight) {
this.data = data;
this.weight = weight;
}
public Node (int weight) {
this.weight = weight;
}
}
buildTree方法的设计








具体代码
/**
* @description: 构建赫夫曼树
*/
public Node[] buildTree (Node [] nodes) {
int s1, s2,p;
int n = nodes.length; // 外结点的数量
int m = 2*n - 1; // 内结点 + 外结点的总数量
Node [] HT = new Node [m]; // 存储结点对象的HT数组
for (int i=0;i<m;i++) HT[i] = new Node(0); // 初始化HT数组元素
for (int i=0;i<n;i++) {
HT[i].data = nodes[i].data;
HT[i].weight = nodes[i].weight; //将给定的权值列表赋给外结点对象
}
for (int i=n;i<m;i++) {
s1 = select(HT,i,0); // 取得HT数组中权值最小的结点对象的下标
s2 = select(HT,i,1); // 取得HT数组中权值次小的结点对象的下标
HT[i].left = s1; // 建立链接
HT[i].right = s2;
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].weight = HT[s1].weight + HT[s2].weight;// 计算当前外结点的权值
selectStart+=2; // 这个变量表示之前“被删除”的最小结点的数量和
}
return HT; // 将处理后的HT数组返回
}
/**
* @description: buildTree方法的用例
*/
public static void main (String [] args) {
Node [] nodes = new Node[4];
nodes[0] = new Node('a',7);
nodes[1] = new Node('b',5);
nodes[2] = new Node('c',2);
nodes[3] = new Node('d',4);
HuffmanTree ht = new HuffmanTree();
Node [] n = ht.buildTree(nodes); // n是构建完毕的赫夫曼树
}
}
select方法的设计
private int select (Node[] HT,int range, int rank)
s1 = select(HT,i,0); // 取得HT数组中权值最小的结点对象的下标
s2 = select(HT,i,1); // 取得HT数组中权值次小的结点对象的下标


/**
* @description: 返回权值排名为rank的结点对象在HT中的下标(按权值从小到大排)
*/
private int select (Node[] HT,int range, int rank) {
Node [] copyNodes = Arrays.copyOf(HT, range);// 将HT[0]~HT[range]拷贝到copyNodes中
QuickSort.sort(copyNodes); // 对copyNodes进行从小到大的快速排序
Node target = copyNodes[rank + selectStart]; // 取得“删除”后权值排名为rank的结点对象
for (int j=0;j<HT.length;j++) {
if (target == HT[j]) return j; // 返回该结点对象在数组HT中的下标
}
return -1;
}


赫夫曼树的应用
等长编码和不等长编码


前缀编码
赫夫曼编码的作用




实现赫夫曼编码(encode)


- 如果P.left==X在HT中的下标,则说明X是P的左分支,说明经过的是 0
- 如果P.right==X在HT中的下标,则说明X是P的右分支,说明经过的是 1


import java.util.Arrays;
/**
* @Author: HuWan Peng
* @Date Created in 22:54 2018/1/14
*/
public class HuffmanTree {
private class HuffmanCode {
char data; // 存放字符,例如 'C'
String bit; // 存放编码后的字符串, 例如"111"
public HuffmanCode (char data, String bit) {
this.data = data;
this.bit = bit;
}
}
/**
* @description: 构建赫夫曼树
*/
public Node[] buildTree (Node [] nodes) {
// 具体代码见上文
}
/**
* @description: 进行赫夫曼编码
*/
public HuffmanCode [] encode(Node [] nodes) {
Node [] HT = buildTree(nodes); // 根据输入的nodes数组构造赫夫曼树
int n = nodes.length;
HuffmanCode [] HC = new HuffmanCode [n];
String bit;
for (int i=0;i<n;i++) { // 遍历各个叶子结点
bit = "";
for (int c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent) { // 从叶子结点上溯到根结点
if(HT[f].left == c) bit= "0" + bit; // 反向编码
else bit= "1" + bit;
}
HC[i] = new HuffmanCode(HT[i].data,bit); // 将字符和对应的编码存储起来
}
return HC;
}
/**
* @description: encode方法的用例
*/
public static void main (String [] args) {
Node [] nodes = new Node[4];
nodes[0] = new Node('A',7);
nodes[1] = new Node('B',5);
nodes[2] = new Node('C',2);
nodes[3] = new Node('D',4);
HuffmanTree ht = new HuffmanTree();
HuffmanCode[] hc = ht.encode(nodes);
// 对A,B,C,D进行编码
for (int i=0;i<hc.length;i++) { // 将赫夫曼编码打印出来
System.out.println(hc[i].data + ":" +hc[i].bit);
}
}
}
A:0
B:10
C:110
D:111
赫夫曼译码(decode)
import java.util.Arrays;
/**
* @Author: HuWan Peng
* @Date Created in 22:54 2018/1/14
*/
public class HuffmanTree {
int selectStart = 0;
private class HuffmanCode {
char data; // 存放字符,例如 'C'
String bit; // 存放编码后的字符串, 例如"111"
public HuffmanCode (char data, String bit) {
this.data = data;
this.bit = bit;
}
}
/**
* @description: 构建赫夫曼树
*/
public Node[] buildTree (Node [] nodes) {
// 代码见上文
}
/**
* @description: 进行赫夫曼译码
*/
public String decode (Node [] nodes, String code) {
String str="";
Node [] HT = buildTree(nodes);
int n =HT.length -1;
for (int i=0;i<code.length();i++) {
char c = code.charAt(i);
if(c == '1') {
n = HT[n].right;
}
else {
n = HT[n].left;
}
if(HT[n].left == 0) {
str+= HT[n].data;
n =HT.length -1;
}
}
return str;
}
/**
* @description: decode方法的用例
*/
public static void main (String [] args) {
Node [] nodes = new Node[4];
nodes[0] = new Node('A',7);
nodes[1] = new Node('B',5);
nodes[2] = new Node('C',2);
nodes[3] = new Node('D',4);
HuffmanTree ht = new HuffmanTree();
// 对 010110111 进行译码
System.out.println(ht.decode(nodes,"010110111"));
}
}
ABCD
全部代码:
Node.java
/**
* @Author: HuWan Peng
* @Date Created in 23:21 2018/1/14
*/
public class Node {
char data;
int weight;
int left, right, parent;
public Node (char data, int weight) {
this.data = data;
this.weight = weight;
}
public Node (int weight) {
this.weight = weight;
}
}
HuffmanTree.java
import java.util.Arrays;
/**
* @Author: HuWan Peng
* @Date Created in 22:54 2018/1/14
*/
public class HuffmanTree {
int selectStart = 0;
private class HuffmanCode {
char data; // 存放字符,例如 'C'
String bit; // 存放编码后的字符串, 例如"111"
public HuffmanCode (char data, String bit) {
this.data = data;
this.bit = bit;
}
}
/**
* @description: 返回权值排名为rank的结点对象在nodes中的下标(按权值从小到大排)
*/
private int select (Node[] HT,int range, int rank) {
Node [] copyNodes = Arrays.copyOf(HT, range);// 将HT[0]~HT[range]拷贝到copyNodes中
QuickSort.sort(copyNodes); // 对copyNodes进行从小到大的快速排序
Node target = copyNodes[rank + selectStart]; // 取得“删除”后权值排名为rank的结点对象
for (int j=0;j<HT.length;j++) {
if (target == HT[j]) return j; // 返回该结点对象在数组HT中的下标
}
return -1;
}
/**
* @description: 构建赫夫曼树
*/
public Node[] buildTree (Node [] nodes) {
int s1, s2,p;
int n = nodes.length; // 外结点的数量
int m = 2*n - 1; // 内结点 + 外结点的总数量
Node [] HT = new Node [m]; // 存储结点对象的HT数组
for (int i=0;i<m;i++) HT[i] = new Node(0); // 初始化HT数组元素
for (int i=0;i<n;i++) {
HT[i].data = nodes[i].data;
HT[i].weight = nodes[i].weight; //将给定的权值列表赋给外结点对象
}
for (int i=n;i<m;i++) {
s1 = select(HT,i,0); // 取得HT数组中权值最小的结点对象的下标
s2 = select(HT,i,1); // 取得HT数组中权值次小的结点对象的下标
HT[i].left = s1; // 建立链接
HT[i].right = s2;
HT[s1].parent = i;
HT[s2].parent = i;
HT[i].weight = HT[s1].weight + HT[s2].weight;// 计算当前外结点的权值
selectStart+=2; // 这个变量表示之前“被删除”的最小结点的数量和
}
return HT; // 将处理后的HT数组返回
}
/**
* @description: 进行赫夫曼编码
*/
public HuffmanCode [] encode(Node [] nodes) {
Node [] HT = buildTree(nodes); // 根据输入的nodes数组构造赫夫曼树
int n = nodes.length;
HuffmanCode [] HC = new HuffmanCode [n];
String bit;
for (int i=0;i<n;i++) { // 遍历各个叶子结点
bit = "";
for (int c=i,f=HT[i].parent;f!=0;c=f,f=HT[f].parent) { // 从叶子结点上溯到根结点
if(HT[f].left == c) bit= "0" + bit; // 反向编码
else bit= "1" + bit;
}
HC[i] = new HuffmanCode(HT[i].data,bit); // 将字符和对应的编码存储起来
}
return HC;
}
/**
* @description: 进行赫夫曼译码
*/
public String decode (Node [] nodes, String code) {
String str="";
Node [] HT = buildTree(nodes);
int n =HT.length -1;
for (int i=0;i<code.length();i++) {
char c = code.charAt(i);
if(c == '1') {
n = HT[n].right;
}
else {
n = HT[n].left;
}
if(HT[n].left == 0) {
str+= HT[n].data;
n =HT.length -1;
}
}
return str;
}
/**
* @description: buildTree方法的用例
*/
public static void main (String [] args) {
Node [] nodes = new Node[4];
nodes[0] = new Node('A',7);
nodes[1] = new Node('B',5);
nodes[2] = new Node('C',2);
nodes[3] = new Node('D',4);
HuffmanTree ht = new HuffmanTree();
System.out.println(ht.decode(nodes,"010110111"));
}
}
QuickSort.java
/**
* @Author: HuWan Peng
* @Date Created in 22:56 2018/1/14
*/
public class QuickSort {
/**
* @description: 交换两个数组元素
*/
private static void exchange(Node [] a , int i, int j) {
Node temp = a[i];
a[i] = a[j];
a[j] = temp;
}
/**
* @description: 切分函数
*/
private static int partition (Node [] a, int low, int high) {
int i = low, j = high+1; // i, j为左右扫描指针
int pivotkey = a[low].weight; // pivotkey 为选取的基准元素(头元素)
while(true) {
while (a[--j].weight>pivotkey) { if(j == low) break; } // 右游标左移
while(a[++i].weight<pivotkey) { if(i == high) break; } // 左游标右移
if(i>=j) break; // 左右游标相遇时候停止, 所以跳出外部while循环
else exchange(a,i, j) ; // 左右游标未相遇时停止, 交换各自所指元素,循环继续
}
exchange(a, low, j); // 基准元素和游标相遇时所指元素交换,为最后一次交换
return j; // 一趟排序完成, 返回基准元素位置
}
/**
* @description: 根据给定的权值对数组进行排序
*/
private static void sort (Node [] a, int low, int high) {
if(high<= low) { return; } // 当high == low, 此时已是单元素子数组,自然有序, 故终止递归
int j = partition(a, low, high); // 调用partition进行切分
sort(a, low, j-1); // 对上一轮排序(切分)时,基准元素左边的子数组进行递归
sort(a, j+1, high); // 对上一轮排序(切分)时,基准元素右边的子数组进行递归
}
public static void sort (Node [] a){ //sort函数重载, 只向外暴露一个数组参数
sort(a, 0, a.length-1);
}
}
【算法】赫夫曼树(Huffman)的构建和应用(编码、译码)的更多相关文章
- Android版数据结构与算法(七):赫夫曼树
版权声明:本文出自汪磊的博客,未经作者允许禁止转载. 近期忙着新版本的开发,此外正在回顾C语言,大部分时间没放在数据结构与算法的整理上,所以更新有点慢了,不过既然写了就肯定尽力将这部分完全整理好分享出 ...
- C#数据结构-赫夫曼树
什么是赫夫曼树? 赫夫曼树(Huffman Tree)是指给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小.哈夫曼树(也称为最优二叉树)是带权路径长度最短的树,权值较大的结点 ...
- Java数据结构和算法(四)赫夫曼树
Java数据结构和算法(四)赫夫曼树 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 赫夫曼树又称为最优二叉树,赫夫曼树的一个 ...
- 【数据结构】赫夫曼树的实现和模拟压缩(C++)
赫夫曼(Huffman)树,由发明它的人物命名,又称最优树,是一类带权路径最短的二叉树,主要用于数据压缩传输. 赫夫曼树的构造过程相对比较简单,要理解赫夫曼数,要先了解赫夫曼编码. 对一组出现频率不同 ...
- 赫夫曼树JAVA实现及分析
一,介绍 1)构造赫夫曼树的算法是一个贪心算法,贪心的地方在于:总是选取当前频率(权值)最低的两个结点来进行合并,构造新结点. 2)使用最小堆来选取频率最小的节点,有助于提高算法效率,因为要选频率最低 ...
- javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题
赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...
- puk1521 赫夫曼树编码
Description An entropy encoder is a data encoding method that achieves lossless data compression by ...
- Python---哈夫曼树---Huffman Tree
今天要讲的是天才哈夫曼的哈夫曼编码,这是树形数据结构的一个典型应用. !!!敲黑板!!!哈夫曼树的构建以及编码方式将是我们的学习重点. 老方式,代码+解释,手把手教你Python完成哈夫曼编码的全过程 ...
- 哈夫曼树Huffman
哈夫曼树处理这样的一种问题: 给出一棵n个叶子的k叉树,每个叶子有一个权值wi,要求最小化∑wi*di di表示,第i个叶子节点到根节点的距离.(一般是边数) 处理方法比较固定. 贪心的思路:我们让权 ...
随机推荐
- final关键字细节
final关键字在java中是一个很重要的关键字,其实按照其字面意思理解,就可以一窥这个关键字端倪,final的本意是最终的.所谓最终的,其最重要的特征就是不能修改,由此衍生出的许多细节均应以这个特征 ...
- 【转载】Java系列笔记(3) - Java 内存区域和GC机制
Java系列笔记(3) - Java 内存区域和GC机制 转载:原文地址http://www.cnblogs.com/zhguang/p/3257367.html 目录 Java垃圾回收概况 Java ...
- Python的下划线_
1.单下划线(_) 通常情况下,单下划线(_)会在以下3种场景中使用: 1.1 在解释器中: 在这种情况下,"_"代表交互式解释器会话中上一条执行的语句的结果.这种用法首先被标准C ...
- java-生成任意格式的json数据
最近研究java的东西.之前靠着自己的摸索,实现了把java对象转成json格式的数据的功能,返回给前端.当时使用的是 JSONObject.fromObject(object) 方法把java对象换 ...
- NodeJs学习笔记(三)
最近在用sails框架写移动后台,马上就过年了,打算总结一下. 1.资源 node官方网站: www.nodejs.org sails官方网站:www.sailsjs.org ...
- 前端模块化:RequireJS(转)
前言 前端模块化能解决什么问题? 模块的版本管理 提高可维护性 -- 通过模块化,可以让每个文件职责单一,非常有利于代码的维护 按需加载 -- 提高显示效率 更好的依赖处理 -- 传统的开发模式,如果 ...
- 动态WebApi
动态WebApi实现了直接对Service的调用,其实没有跨过ApiController,只是我们自己创建出ApiController 实现主要分以下几步 一 对默认WebApi服务的替换 ApiGl ...
- 使用Libmicrohttpd搭建内嵌(本地)服务器
Libmicrohttpd简介 GNU Libmicrohttpd是一个用来在项目中内嵌http服务器的C语言库,它具有以下几个非常鲜明的特点: C语言库,小而快. API非常简单,且都是可重入的. ...
- redis hash结构 遍历某一个key下所有的(field,values)的方法
本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/95 redis的hash结构中存储了如下的数据: $input ...
- ArcGIS 网络分析[8.6] 资料6 创建网络分析图层及进行路径分析
基于上篇所介绍的内容,就说说如何利用访问到的网络数据集,在Map中添加网络数据集图层.创建网络分析图层中的路径图层,并执行路径分析示例.