哈夫曼树(三)之 Java详解

前面分别通过CC++实现了哈夫曼树,本章给出哈夫曼树的java版本。

目录

1. 哈夫曼树的介绍
2.
哈夫曼树的图文解析
3. 哈夫曼树的基本操作
4. 哈夫曼树的完整源码

转载请注明出处:http://www.cnblogs.com/skywang12345/

更多内容:数据结构与算法系列
目录

哈夫曼树的介绍

Huffman
Tree,中文名是哈夫曼树或霍夫曼树,它是最优二叉树。

定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若树的带权路径长度达到最小,则这棵树被称为哈夫曼树。
这个定义里面涉及到了几个陌生的概念,下面就是一颗哈夫曼树,我们来看图解答。

(01) 路径和路径长度

定义:在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。


例子:100和80的路径长度是1,50和30的路径长度是2,20和10的路径长度是3。

(02)
结点的权及带权路径长度

定义:若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

例子:节点20的权是3,它的带权路径长度= 路径长度 * 权 = 3 * 20 =
60。

(03) 树的带权路径长度

定义:树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

例子:示例中,树的WPL= 1100 + 280 + 320 + 310 =
100 + 160 + 60 + 30 = 350。

比较下面两棵树

上面的两棵树都是以{10, 20, 50,
100}为叶子节点的树。

左边的树WPL=2*10 + 2*20 +
2*50 + 2*100 = 360
右边的树WPL=350

左边的树WPL >
右边的树的WPL。你也可以计算除上面两种示例之外的情况,但实际上右边的树就是{10,20,50,100}对应的哈夫曼树。至此,应该堆哈夫曼树的概念有了一定的了解了,下面看看如何去构造一棵哈夫曼树。

哈夫曼树的图文解析

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。
n个权值分别设为 w1、w2、…、wn,哈夫曼树的构造规则为:

1.
将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

2.
在森林中选出根结点的权值最小的两棵树进行合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

3. 从森林中删除选取的两棵树,并将新树加入森林;
4.
重复(02)、(03)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

以{5,6,7,8,15}为例,来构造一棵哈夫曼树。

第1步:创建森林,森林包括5棵树,这5棵树的权值分别是5,6,7,8,15。


第2步:在森林中,选择根节点权值最小的两棵树(5和6)来进行合并,将它们作为一颗新树的左右孩子(谁左谁右无关紧要,这里,我们选择较小的作为左孩子),并且新树的权值是左右孩子的权值之和。即,新树的权值是11。
然后,将"树5"和"树6"从森林中删除,并将新的树(树11)添加到森林中。

第3步:在森林中,选择根节点权值最小的两棵树(7和8)来进行合并。得到的新树的权值是15。
然后,将"树7"和"树8"从森林中删除,并将新的树(树15)添加到森林中。

第4步:在森林中,选择根节点权值最小的两棵树(11和15)来进行合并。得到的新树的权值是26。
然后,将"树11"和"树15"从森林中删除,并将新的树(树26)添加到森林中。

第5步:在森林中,选择根节点权值最小的两棵树(15和26)来进行合并。得到的新树的权值是41。
然后,将"树15"和"树26"从森林中删除,并将新的树(树41)添加到森林中。
此时,森林中只有一棵树(树41)。这棵树就是我们需要的哈夫曼树!

哈夫曼树的基本操作

哈夫曼树的重点是如何构造哈夫曼树。本文构造哈夫曼时,用到了以前介绍过的"(二叉堆)最小堆"。下面对哈夫曼树进行讲解。

1.
基本定义


public class HuffmanNode implements Comparable, Cloneable {
protected int key; // 权值
protected HuffmanNode left; // 左孩子
protected HuffmanNode right; // 右孩子
protected HuffmanNode parent; // 父结点

protected HuffmanNode(int key, HuffmanNode left, HuffmanNode right, HuffmanNode parent) {
this.key = key;
this.left = left;
this.right = right;
this.parent = parent;
}

@Override
public Object clone() {
Object obj=null;

try {
obj = (HuffmanNode)super.clone();//Object 中的clone()识别出你要复制的是哪一个对象。
} catch(CloneNotSupportedException e) {
System.out.println(e.toString());
}

return obj;
}

@Override
public int compareTo(Object obj) {
return this.key - ((HuffmanNode)obj).key;
}
}

HuffmanNode是哈夫曼树的节点类。

public class Huffman {

private HuffmanNode mRoot; // 根结点

...
}

Huffman是哈夫曼树对应的类,它包含了哈夫曼树的根节点和哈夫曼树的相关操作。

2.
构造哈夫曼树


/*
* 创建Huffman树
*
* @param 权值数组
*/
public Huffman(int a[]) {
HuffmanNode parent = null;
MinHeap heap;

// 建立数组a对应的最小堆
heap = new MinHeap(a);

for(int i=0; i<a.length-1; i++) {
HuffmanNode left = heap.dumpFromMinimum(); // 最小节点是左孩子
HuffmanNode right = heap.dumpFromMinimum(); // 其次才是右孩子

// 新建parent节点,左右孩子分别是left/right;
// parent的大小是左右孩子之和
parent = new HuffmanNode(left.key+right.key, left, right, null);
left.parent = parent;
right.parent = parent;

// 将parent节点数据拷贝到"最小堆"中
heap.insert(parent);
}

mRoot = parent;

// 销毁最小堆
heap.destroy();
}

首先创建最小堆,然后进入for循环。

每次循环时:

(01)
首先,将最小堆中的最小节点拷贝一份并赋值给left,然后重塑最小堆(将最小节点和后面的节点交换位置,接着将"交换位置后的最小节点"之前的全部元素重新构造成最小堆);

(02) 接着,再将最小堆中的最小节点拷贝一份并将其赋值right,然后再次重塑最小堆;
(03)
然后,新建节点parent,并将它作为left和right的父节点;
(04) 接着,将parent的数据复制给最小堆中的指定节点。

二叉堆中已经介绍过堆,这里就不再对堆的代码进行说明了。若有疑问,直接参考后文的源码。其它的相关代码,也Please
RTFSC(Read The Fucking Source Code)!

哈夫曼树的完整源码

哈夫曼树的源码共包括4个文件。

1. 哈夫曼树的节点类(HuffmanNode.java)

2. 哈夫曼树的实现文件(Huffman.java)

3. 哈夫曼树对应的最小堆(MinHeap.java)

4. 哈夫曼树的测试程序(HuffmanTest.java)

哈夫曼树(三)之 Java详解,布布扣,bubuko.com

时间: 2024-08-23 10:44:12

哈夫曼树(三)之 Java详解的相关文章

数据结构与算法 -- 哈夫曼树思想与创建详解1

PS:什么是哈夫曼树? 给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近. 计算规则: 假设一组权值,一个权值是一个结点,12  34  2  5  7  ,在这其中找出两个最小的权值,然后组成一个新的权值,再次找出最小的权值结点.如图: 问题: 1:如果程序找出两个最小的权值,把两个权值最小的相加结果再次添加到数组中呢. 2:完成第一步后,又

基础数据结构-二叉树-赫夫曼树的解码(详解)

本篇是上一篇赫夫曼树构建与编码的后续,稍微详细讲一下解码的算法. Huffman解码算法流程: 1.定义指针p指向赫夫曼树结点,实际是记录结点数组的下标: 2.定义指针i指向编码串,定义ch逐个取编码串的字符: 3.初始化:读入编码串,设置p指向根结点,i为0: 4.执行以下循环: a)取编码串的第i个字符放入ch: b)如果ch是字符0,表示往左孩子移动,则p跳转到右孩子: c)如果ch是字符1,表示往右孩子移动,则p跳转到右孩子: d)如果ch非0非1,表示编码串有错误,输出error表示解

Kruskal算法(三)之 Java详解

前面分别通过C和C++实现了克鲁斯卡尔,本文介绍克鲁斯卡尔的Java实现. 目录 1. 最小生成树 2. 克鲁斯卡尔算法介绍 3. 克鲁斯卡尔算法图解 4. 克鲁斯卡尔算法分析 5. 克鲁斯卡尔算法的代码说明 6. 克鲁斯卡尔算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 最小生成树 在含有n个顶点的连通图中选择n-1条边,构成一棵极小连通子图,并使该连通子图中n-1条边上权值之和达到最小,则称其为连通网的

Dijkstra算法(三)之 Java详解

前面分别通过C和C++实现了迪杰斯特拉算法,本文介绍迪杰斯特拉算法的Java实现. 目录 1. 迪杰斯特拉算法介绍 2. 迪杰斯特拉算法图解 3. 迪杰斯特拉算法的代码说明 4. 迪杰斯特拉算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 迪杰斯特拉算法介绍 迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径. 它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想

(哈夫曼树)HuffmanTree的java实现

参考自:http://blog.csdn.net/jdhanhua/article/details/6621026 哈夫曼树 哈夫曼树(霍夫曼树)又称为最优树. 1.路径和路径长度在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径.通路中分支的数目称为路径长度.若规定根结点的层数为1,则从根结点到第L层结点的路径长 2.结点的权及带权路径长度若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权.结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积

Floyd算法(三)之 Java详解

前面分别通过C和C++实现了弗洛伊德算法,本文介绍弗洛伊德算法的Java实现. 目录 1. 弗洛伊德算法介绍 2. 弗洛伊德算法图解 3. 弗洛伊德算法的代码说明 4. 弗洛伊德算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 弗洛伊德算法介绍 和Dijkstra算法一样,弗洛伊德(Floyd)算法也是一种用于寻找给定的加权图中顶点间最短路径的算法.该算法名称以创始人之一.1978年图灵奖获得者.斯坦福大学计

Prim算法(三)之 Java详解

前面分别通过C和C++实现了普里姆,本文介绍普里姆的Java实现. 目录 1. 普里姆算法介绍 2. 普里姆算法图解 3. 普里姆算法的代码说明 4. 普里姆算法的源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 普里姆算法介绍 普里姆(Prim)算法,是用来求加权连通图的最小生成树的算法. 基本思想 对于图G而言,V是所有顶点的集合:现在,设置两个新的集合U和T,其中U用于存放G的最小生成树中的顶点,T存放G的最

邻接表有向图(三)之 Java详解

前面分别介绍了邻接表有向图的C和C++实现,本文通过Java实现邻接表有向图. 目录 1. 邻接表有向图的介绍 2. 邻接表有向图的代码说明 3. 邻接表有向图的完整源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 邻接表有向图的介绍 邻接表有向图是指通过邻接表表示的有向图. 上面的图G2包含了"A,B,C,D,E,F,G"共7个顶点,而且包含了"<A,B>,<B,C>,

邻接矩阵有向图(三)之 Java详解

前面分别介绍了邻接矩阵有向图的C和C++实现,本文通过Java实现邻接矩阵有向图. 目录 1. 邻接矩阵有向图的介绍 2. 邻接矩阵有向图的代码说明 3. 邻接矩阵有向图的完整源码 转载请注明出处:http://www.cnblogs.com/skywang12345/ 更多内容:数据结构与算法系列 目录 邻接矩阵有向图的介绍 邻接矩阵有向图是指通过邻接矩阵表示的有向图. 上面的图G2包含了"A,B,C,D,E,F,G"共7个顶点,而且包含了"<A,B>,<