概念
-
给定n个权值作为n个叶子节点,构造一颗二叉树,若该数的代全路径长度(wpl)达到最小,称这样的的二叉树为最优二叉树,也成霍夫曼树
-
霍夫曼树是带权路径长度最短的树,权值较大的节点离根较近
-
路径和路径长度:
-
在一棵树中,从一个结点往下可以达到的孩子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根节点到L层结点的路径长度为L-1
-
-
结点的权及带权路径长度:
-
若将树中结点赋给一个有着某种含义的数值,则这个数值成为该节点的权。结点的带权路径长度为:从根节点到该节点之间的路径长度与该节点的权的乘积
-
-
树的带权路径长度:树的带权路径长度规定为所有叶子节点的带权路径长度之和,记为WPL(weighted path length),权值越大的节点离根节点越近。
原理
因为带权路径长度是权值*路径长度,要使带权路径长度最小,节点的权值越大,其路径长度就要越小
-
给定一个序列,将其从小到大排序,这样我们序列的前两个就是权值最小的两个元素
-
创建一个新的节点,其权值为权值最小的两个元素的权值之和,然后将这两个最小元素,分别放到新节点的左右子节点上,并将新节点的权值放回序列,代替原来的两个元素
-
重新排序后重复上述操作,直到序列中只剩一个节点,即构建完毕
-
这样创建出的赫夫曼树的根结点为所有节点的权值之和,根结点的两个子节点是最大节点与其他节点的权值之和构建的新节点,以此类推,权值越大,越靠近根结点,路径越短
赫夫曼编码
-
赫夫曼编码是哈夫曼树在电讯通信中的经典应用之一
-
赫夫曼编码广泛地用于数据文件压缩。其压缩率通常都在20%~90%之间
-
赫夫曼码是可变字长编码的一种
-
构建好赫夫曼树后,按照左0右1的规则生成编码,因为生成的赫夫曼树后,每个编码都是叶子节点,所以不会产生有两个编码有同样前缀的情况(即每个编码的父节点都不可能是另一个编码),即赫夫曼编码是前缀编码
代码
class HuffNode implements Comparable<HuffNode> {
int value;
HuffNode left;
HuffNode right;
char c;
String huffCode;
public HuffNode(int value, char c) {
this.value = value;
this.left = null;
this.right = null;
this.c = c;
this.huffCode = "";
}
public HuffNode(int value) {
this.value = value;
this.left = null;
this.right = null;
this.huffCode = "";
}
public HuffNode() {
this.value = 0;
this.left = null;
this.right = null;
this.huffCode = "";
}
@Override
public String toString() {
if (left != null && right != null) {
return "HuffNode{ " +
" value=" + value +
" left=" + left.value +
" right=" + right.value +
" c=" + c +
" huffCode=" + huffCode +
" }";
}else if (left != null && right == null){
return "HuffNode{ " +
" value=" + value +
" left=" + left.value +
" c=" + c +
" huffCode=" + huffCode +
" }";
}else {
return "HuffNode{ " +
" value=" + value +
" c=" + c +
" huffCode=" + huffCode +
" }";
}
}
/**
* 先序遍历
*/
public void preOrder() {
System.out.println(this);
if (this.left != null) {
this.left.preOrder();
}
if (this.right != null) {
this.right.preOrder();
}
}
/**
* 设置哈弗曼编码,路径左0右1
* 哈弗曼编码是前缀编码的原因为:生成哈夫曼树时每个元素都为叶子节点
* @param c 根据字符查询
* @param code 存储路径信息,最后保存到huffCode中
*/
public void setHuffCode(char c , String code) {
if (this.c == c) {
String str = "";
for (int i = 1; i < code.length(); i++) {
str = str + code.charAt(i);
}
this.huffCode = str;
return;
}
// 左0
if (this.left != null) {
code = code+"0";
this.left.setHuffCode(c,code);
}
// 右1
if (this.right != null) {
code = code+"1";
this.right.setHuffCode(c,code);
}
}
/**
* 实现Comparable接口
* 实现方法,此为从小到大排序
*
* @param o
* @return
*/
@Override
public int compareTo(HuffNode o) {
return this.value - o.value;
}
}
class HuffmaTree {
private ArrayList<HuffNode> huffNodeArrayList ;
private HuffNode[] arrHuff;
private HuffNode root;
public void setHuffNodeArrayList(ArrayList<HuffNode> huffNodeArrayList) {
this.huffNodeArrayList = new ArrayList<HuffNode>();
for (int i = 0 ; i < huffNodeArrayList.size() ; i++) {
HuffNode node = huffNodeArrayList.get(i);
this.huffNodeArrayList.add(node);
}
}
public void list() {
Iterator iterator = huffNodeArrayList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
public void setRoot(HuffNode root) {
this.root = root;
}
public void preOrder() {
if (root == null) {
System.out.println("空");
}else {
root.preOrder();
}
}
/**
* 根据数组中的值放置arrHuff
* @param arr
*/
public void setArrHuff(int[] arr) {
arrHuff = new HuffNode[arr.length];
for (int i = 0 ; i < arr.length ; i++) {
HuffNode node = new HuffNode(arr[i]);
arrHuff[i] = node;
}
}
public HuffNode[] getArrHuff() {
return arrHuff;
}
public ArrayList<HuffNode> getHuffNodeArrayList() {
return huffNodeArrayList;
}
/**
* 构造哈夫曼树,所有叶子节点的带权路径之和最小的树(路径长度*权值)
* @param arr
* @return 返回最优二叉树的根结点
*/
public HuffNode createHuffmanTree_2(int[] arr) {
ArrayList<HuffNode> arrayList = new ArrayList();
for (int value : arr) {
arrayList.add(new HuffNode(value));
}
while (arrayList.size() > 1) {
// 1. 将数组从小到大排序
Collections.sort(arrayList);
// 2. 选择权值最小的两个节点组成树,其根结点为两子节点权值之和
HuffNode leftNode = arrayList.get(0);
HuffNode rightNode = arrayList.get(1);
HuffNode parent = new HuffNode(leftNode.value + rightNode.value);
parent.right = rightNode;
parent.left = leftNode;
// 3. 将根节点权值放回数组中,并重复上述操作
arrayList.remove(leftNode);
arrayList.remove(rightNode);
arrayList.add(parent);
}
return arrayList.get(0);
}
/**
* 构造哈夫曼树,所有叶子节点的带权路径之和最小的树(路径长度*权值)
* @param arr
*/
public HuffNode createHuffmanTree_1(int[] arr) {
// 1. 将数组从小到大排序
QuickSort quickSort = new QuickSort();
quickSort.quickSort(arr,0,arr.length-1);
setArrHuff(arr);
for (int i = 0 ; i < arr.length-1 ; i++) {
// 2. 选择权值最小的两个节点组成树,其根结点为两子节点权值之和
int value = arrHuff[i].value + arrHuff[i+1].value;
HuffNode node = new HuffNode(value);
node.left = arrHuff[i];
node.right = arrHuff[i+1];
// 3. 将根节点权值放回数组中,并重复上述操作
arrHuff[i+1] = node;
for (int j = i+1 ; j < arrHuff.length-1 ; j++) {
for (int k = i+1 ; k < arrHuff.length-1 ; k++) {
if (arrHuff[k].value>arrHuff[k+1].value) {
HuffNode temp = arrHuff[k];
arrHuff[k] = arrHuff[k+1];
arrHuff[k+1] = temp;
}
}
}
}
return arrHuff[arrHuff.length-1];
}
/**
* 构造哈夫曼树,所有叶子节点的带权路径之和最小的树(路径长度*权值)
* @param arr
* @return 返回最优二叉树的根结点
*/
public HuffNode createHuffmanTree_3(int[] arr , char[] letter) {
ArrayList<HuffNode> arrayList = new ArrayList();
for (int i = 0 ; i < arr.length ; i++) {
arrayList.add(new HuffNode(arr[i],letter[i]));
}
setHuffNodeArrayList(arrayList);
while (arrayList.size() > 1) {
// 1. 将数组从小到大排序
Collections.sort(arrayList);
// 2. 选择权值最小的两个节点组成树,其根结点为两子节点权值之和
HuffNode leftNode = arrayList.get(0);
HuffNode rightNode = arrayList.get(1);
HuffNode parent = new HuffNode(leftNode.value + rightNode.value);
parent.right = rightNode;
parent.left = leftNode;
// 3. 将根节点权值放回数组中,并重复上述操作
arrayList.remove(leftNode);
arrayList.remove(rightNode);
arrayList.add(parent);
}
return arrayList.get(0);
}
/**
* 设置哈夫曼编码
* @param c 字符,根据字符查找,并记录路径
* @param code
*/
public void setHuffCode(char c , String code) {
if (this.root==null) {
System.out.println("空");
}else {
root.setHuffCode(c,code);
}
}
}
public class HuffmanTreeDemo {
/**
* 统计字符串中每个字符出现的次数
* @param str
* @return
*/
public static char[][] countTimes(String str) {
char[][] letter = new char[2][str.length()];
ArrayList arrayList = new ArrayList();
for (int i = 0 ; i < str.length() ; i++) {
arrayList.add(str.charAt(i));
}
int temp = 0 ;
while (arrayList.size() > 0) {
int i = 0;
// 每次根据队列的第一个元素进行查询,若有相同元素计数++,并且将其删除
letter[0][temp] = (char) arrayList.get(0);
while (i < arrayList.size()) {
if (letter[0][temp] == (char)arrayList.get(i)) {
letter[1][temp]++;
arrayList.remove(arrayList.get(i));
}else {
i++;
}
}
temp++;
}
char[][] le = new char[2][temp];
for (int i = 0 ; i < temp ; i++) {
le[0][i] = letter[0][i];
le[1][i] = letter[1][i];
}
return le;
}