最近研究二叉树,比较经典的树就是哈夫曼树了,所以研究一下它的构建以及哈夫曼编码,恶补一下数据结构的知识。
有一段密文:aabbccabcacb,解析为电码传输,只能为0、1来表示
例如
a 0
b 1
c 01
d 10
… …
那么aabc….可以表示为00101,但是在解析的时候发现0 01 10可以出现混乱,001可以解析为 ac 或者 aab,这样就会导致数据不唯一。因此可以用二叉树来保证数据唯一。
左边用0表示,右边用1表示,那么abcd的编码如下:
A 0
B 10
C 110
D 111
aabc….
可以表示为0010110,在解析的时候就不会出现混乱,因为数据是唯一表示的。
那么问题又来了,如果a编码出现的次数是1次,b编码的出现次数是1次,c出现次数是10000
那么aabc的数据长度:1*1 +1*1+2*1+3*10000+…
那么如何进行优化,是这个编码的长度最短?可以通过调整数节点的位置,例如下:
左边用0表示,右边用1表示,那么abcd的编码如下:
B 0
A 10
C 110
D 111
那么aabc…的编码将会变成
2*1+2*1+1*10000+…
这样就可以使得在保证编码数据唯一的情况下,编码长度最小。这就是一个哈夫曼编码,这课树就是哈夫曼树
哈夫曼树定义:指对于一组带有确定权值的叶节点,构造的具有最小带权路径长度的二叉树
Wpl=(w1 * l1+w2*l2 + ·····)
构造哈夫曼树:
步骤:先对数组A排序,后选择前两个最小的值作为左右节点,它们之和是其根节点(父节点node),node入数组A,直到数组A只剩下一个元素。
结束循环
2 5 23 43 55 65 71 123
总度数:
Sum=2*5+5*5+23*4+43*3+55*3+65*3+71*2+123*2
=10+ 25+ 92 +129 +165 +195 +142 + 246
=1004度
代码实现:
/**
* 类节点
* @author breeze
*
* @param <E>
*/
class TreeNode<E> implements Comparable<TreeNode<E>>{
E data;
int weight;
TreeNode<E>left;
TreeNode<E>right;
TreeNode<E> parent;
public TreeNode(E data,int weight) {
this.data = data;
this.weight=weight;
left=null;
right=null;
parent=null;
}
}
/**
* 创建哈夫曼树
* @param list
*/
public void createHafumanTree(ArrayList<TreeNode<Integer>> list){
while(list.size()>1){
Collections.sort(list);
TreeNode<Integer> left =list.remove(0);
TreeNode<Integer> right =list.remove(0);
TreeNode<Integer> parent =new TreeNode<Integer>(data,left.weight+right.weight);
parent.left=left;
parent.right=right;
left.parent=parent;
right.parent=parent;
list.add(parent);
}
if(!list.isEmpty()){
this.root=list.get(0);
}else{
this.root=null;
}
}
那么哈夫曼编码怎么获取到?
这里使用的方法是先搜索树,查找到要编码的节点位置,然后通过判断父节点的分支方向并
使用栈来保存结果
步骤:
1、通过搜索树找到节点(这里使用广度优先搜索)。
2、循环判断该节点父节点是否为空
3、如果该节点是左分支则入栈0,如果其是右分支则入栈1,向上移动,循环进入步骤二。
例如:查找 71 节点的哈夫曼编码
步骤二:判断其父节点给null,则直接退出
最后然后遍历栈,得到的就是哈夫曼编码了
71的哈夫曼编码是 00
具体代码如下:
/**
* 普通树查找
* 广度优先遍历、当然还有前、中、后序遍历树(递归或者栈实现)
* @param data
* @return
*/
public TreeNode<Integer> sreachNode(int data){
TreeNode<Integer> result=null;
if(null==root){
return null;
}else{
LinkedList<TreeNode<Integer>> list =new LinkedList<>();
list.addFirst(root);
while(!list.isEmpty()&&result==null){
TreeNode<Integer> node =list.pollFirst();
if(node.data==data)
result=node;
if(node.left!=null){
list.addLast(node.left);
}
if(node.right!=null){
list.addLast(node.right);
}
}
}
return result;
}
/**
* 栈前序遍历
* @param node
*/
public void preStackLook(TreeNode<Integer> node){
if(null==node){
return ;
}else{
Stack<TreeNode<Integer>> stack=new Stack<>();
stack.push(node);
while(!stack.isEmpty()){
node=stack.pop();
System.out.print(node.data +" ");
if(null!=node.right)
stack.push(node.right);
if(null!=node.left)
stack.push(node.left);
}
}
}
/**
* 递归前序遍历
* @param root
*/
public void preOrderLook(TreeNode<Integer> root){
if(null==root){
return ;
}
System.out.print(root.data+" ");
preOrderLook(root.left);
preOrderLook(root.right);
}
/**
* 递归中序遍历
* @param root
*/
public void midOrderLook(TreeNode<Integer> root){
if(null==root){
return ;
}
midOrderLook(root.left);
System.out.print(root.data+" ");
midOrderLook(root.right);
}
/**
* 递归前后序遍历
* @param root
*/
public void breforeOrderLook(TreeNode<Integer> root){
if(null==root){
return ;
}
breforeOrderLook(root.left);
breforeOrderLook(root.right);
System.out.print(root.data+" ");
}
/**
* 获取哈夫曼编码
* @param data
* @return
*/
public String getCode(Integer data){
TreeNode<Integer> node =sreachNode(data);
String result="";
if(node==null)
return null;
else{
Stack<Integer> stack =new Stack<>();
while(node.parent!=null){
if(node.parent.left==node){
stack.push(0);
}else{
stack.push(1);
}
node=node.parent;
}
for(;!stack.isEmpty();){
result+=stack.pop();
}
}
return result;
}
总结:以上就是哈夫曼树的由来、创建、遍历、获取哈夫曼编码等的过程了。比较简单,记录一下,方便以后查看