Huffman Tree
赫夫曼树又称为最优二叉树,是一类带权路径长度最短的树。
基本概念
首先我们得了解路径和路径长度的概念,书上对他们的定义是这样的。
- 从树中一个节点到另外一个节点之间的分支构成这两个节点之间的路径。
- 路径上的分支数目称为路径长度。
也就是说,一个节点如何到另一个节点,通过哪几个线段,那些线段叫做路径。
而路径长度就是你从这个节点到两一个节点经过几条路,这就是路径长度。
赫夫曼树
那么问题来了,什么是我们说的最优二叉树,即赫夫曼树呢?
所谓的赫夫曼树,也就是从根节点出发,到所有叶子节点所经过的路径长度与权重相乘之和最小的树。
文字理解很费脑,那我们就看看下面的图
这两个树,并不是赫夫曼树,但是同样的我们是不是可以求他的权重路径之和
W1=3x2+1x3+7x3+5x2+8x2=56
W2=3x3+1x1+7x3+5x3+8x3=69
接下来我们看赫夫曼树他的权重和
W3=3x3+1x3+5x2+7x2+8x2=52
看!这是不是明显的比上面两个小了!
所以,我们如何把他们变换成赫夫曼树呢
赫夫曼一般算法
- 根据给定的n个权值构成n个二叉树集合,其中每个二叉树只有一个带权值为w的根节点,左右子树均为空。
- 在集合当中寻找两个最小权值的根节点构成一个新的二叉树,而且新的二叉树权值为他们两个的和,其左右孩子为她们两个。
- 在集合当中删除原有的两个树,并插入新生成的树。
- 重复2-3直至集合当中只有1棵树输出
图解
如图
在这里我们要直到一个点,对于新生成的二叉树,他左右子树的顺序是没有关系的,也就是说,无论你把1作为左子树还是2作为左子树,对于他的结果毫无影响。
算法实现
首先创造我们所需要的节点类型
public class HuffmanTree<AnyType> {
//create the node we need
private class HNode<AnyType>{
private AnyType data;
private int wight;
private HNode<AnyType> left;
private HNode<AnyType> right;
public HNode(AnyType data,int wight){this(data,wight,null,null);}
public HNode(AnyType data,int wight,HNode<AnyType> left,HNode<AnyType> right){
this.data=data;
this.wight=wight;
this.left=left;
this.right=right;
}
}
其次我们要进行初始化节点数组 bigger是用来扩容的,如果当前森林当中的数量已经大于森林的一半,则对森林进行扩容,扩容大小为原本的两倍。
private static final int BASIC_CAPACITY = 10;
//forest is a list which we need use
private HNode<AnyType>[] Forest;
private int size;
//basic operation
public HuffmanTree(){clear();}
public void clear(){
Forest=new HNode[BASIC_CAPACITY];
for (int i = 0; i <Forest.length ; i++)
Forest[i]=null;
size=0;
}
public boolean isEmpty(){return size==0;}
public int nodeSize(){return size;}
private void bigger(int size){
HNode[] old=Forest;
Forest=new HNode[size];
for (int i = 0; i < size; i++) {
if (i<old.length)
Forest[i]=old[i];
else
Forest[i]=null;
}
}
基本的增删改查操作。
插入
//insert node
public void insert(AnyType data,int w){
//if size*2>basic capacity then make forest double
if(size*2+1> Forest.length)
bigger(Forest.length*2);
Forest[size++]=new HNode<>(data,w);
}
获得指定位置权值
public int getWight(int idx){return getNode(idx).wight;}
private HNode<AnyType> getNode(int idx){
if(idx<0||idx>size)
throw new IndexOutOfBoundsException();
return Forest[idx];
}
删除树
public void delete(int idx){
if(idx<0||idx>size)
throw new IndexOutOfBoundsException();
Forest[idx]=null;
size--;
}
打印
//print
public void printAll(){
for (int i = 0; i < size; i++) {
print(i);
System.out.println();
System.out.println("-------------");
}
}
public void print(int idx){
if(idx<0||idx>size)
throw new IndexOutOfBoundsException();
HNode<AnyType> temp=getNode(idx);
System.out.println("权重为:"+temp.wight);
System.out.print("数据为:");
print(temp);
}
private void print(HNode<AnyType> node){
if(node==null)
return;
if (node.data!=null)
System.out.print(node.data+" ");
print(node.left);
print(node.right);
}
Huffman 实现
下面是对森林当中的二叉树转变为最优二叉树的算法,由于获得两个权值最小的树要遍历森林,所以我用了归并排序来进行操作。归并的原理可以看这里 归并排序
在这里我们递归的出口是当森林集合当中只有一个树的情况直接返回。
否则每次把两个树合成一个树的时候,删除原来的树,把新生成的放在第一个位置,把最后一个位置的树,放在第二个位置。每次进来都要排序一下。
//become huffman tree
public void beHuffman(){
if (size==1)
return;
//sort make the min is first
GBs();
int wightSum=Forest[0].wight+Forest[1].wight;
HNode<AnyType> tempN=new HNode<>(null,wightSum,Forest[0],Forest[1]);
Forest[0]=tempN;
Forest[1]=Forest[size-1];
Forest[--size]=null;
beHuffman();
}
归并算法
这里是归并排序算法的实现,目的是为了使得我们进行变换的时候,第一个位置和第二个位置永远处于权值最小的。
private void GBs(){GBs(0,size-1);}
private void GBs(int lP,int rP){
int center=(lP+rP)/2;
if(lP<rP){
GBs(lP,center);
GBs(center+1,rP);
Sort(lP,center+1,rP);
}
}
//刚开始进来时,两个为一组,到后面进来时,两个数组默认有序
private void Sort(int leftPos,int rightPos,int End){
int leftEnd=rightPos-1;
int temp=0;
int number=End+1-leftPos;
HNode<AnyType>[] tempF=new HNode[number];
while(leftPos<=leftEnd&&rightPos<=End){
if (Forest[leftPos].wight<Forest[rightPos].wight)
tempF[temp++]=Forest[leftPos++];
else
tempF[temp++]=Forest[rightPos++];
}
//上一步操作总会有一个还没放入完的,所以再把没放完的重新放入暂时数组当中
while (leftPos<=leftEnd)
tempF[temp++]=Forest[leftPos++];
while (rightPos<=End)
tempF[temp++]=Forest[rightPos++];
//把tempF中的结果放入Forest当中
temp--;
for (int i=0;i<number;End--,temp--,i++)
Forest[End]=tempF[temp];
}
}