哈夫曼树与哈弗曼编码

在这里主要回顾一下:哈夫曼树带权路径的计算哈夫曼树的构造java实现以及哈弗曼编码应用

 

相关定义:

哈夫曼树(Huffman tree):又称最优二叉树,就是给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,则就称为哈夫曼树。

权值:哈夫曼树的权值是自己定义的,他的物理意义表示数据出现的次数、频率。可以用树的每个结点数据域data存放一个特定的数表示它的值。

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


结点的带权路径长度为:从根结点到该结点之间的路径长度该结点的权值乘积。  树中所有叶子节点的带权路径长度之和,WPL= (W1*L1+W2*L2+W3*L3+...+Wn*Ln)。

图解:

`DJYEZ{8KCBY%SGFN45C[$7

 

哈夫曼树构造过程:

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

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点),按照权值排序;

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

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

 

图解:

[PO43RCR[XFR~R}~1TA%2~1

注意:哈夫曼树只含有度为0和度为2的点。刚开始学的时候又弄混过,记录下:

 

5V1FC(R9AMPWIM3Q2[8PA$0

(7个字符在电文总出现的概率分别是0.10、0.20、0.05、0.15、0.07、0.13、0.30,由此构造的哈夫曼树,所以的节点要么度为0,要么度为2)

应用:

主要用于通信编码

       在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中用到 4 个不同字符{A,C,S,T,},它们在电文中出现的次数分别为{ 7 , 2 , 4 , 5} 。把 {7 , 2 , 4 , 5} 当做 4 个叶子的权值构造哈夫曼树,并且在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码,如果不这样的话,每个字符的都按照相同长度的二进制码长,则最后得到的二进制码总长度肯定会比较长,所以也经常用哈夫曼树去压文件。

如图所示:

 

FZMQWBNZ56_4[(3)%}DNN%M

(图中18改成19)

这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码,好神奇。也很容易理解,但是这种非等长的编码方式给读码带来了困难,这给代码实现带来了困难。

代码:

import java.util.ArrayDeque;  
import java.util.ArrayList;  
import java.util.List;  
import java.util.Queue;  
import java.util.Scanner;
  
/** 
 * 时间:2013年12月1号
 * 目的:实现哈夫曼树
 * 介绍:
 *     树的节点类Node<T> ,
 *     利用列表list对节点排序方法sort(List<Node> list) ,
 *     给定一个list,构造哈夫曼树的方法crateHufManTree(List<Node> list),
 *     给出根节点,广度优先遍历一棵树的方法deepFirst(Node root)
 *     一个测试实例   
 * @author lm 
 */

public class HufManTree{
    public static class Node<T> 
    {
        T date;               //参数类型
        int weight;           //节点权值 
        
        Node<T> lChild;
        Node<T> rChild;
        
        public Node(T date,int weight){
            this.date = date;
            this.weight = weight;
        }
        
        public String toString()
        {return  date +"  " + weight ;}
        
        //比较两节点的大小
        public boolean compareTo(Node<T> node)
        {
            if(this.weight < node.weight)
            {return true;        }
            return false;
        }
    }
    //排序,从大到小,用列表,线性存储各个节点,这里是用set方法交换两个元素
    public static  void sort(List<Node> list)
    {
        for(int i=0;i<list.size();i++)
            for(int j=i+1;j<list.size();j++)
            {
                if(list.get(i).compareTo(list.get(j)))
                {
                    Node node = list.get(i);
                    list.set(i,list.get(j));
                    list.set(j, node);
                }
            }
    }

    //有了前面的准备,下面就是创建过程
    @SuppressWarnings("unchecked")
    public  static Node crateHufManTree(List<Node> list)
    {
        while(list.size()>1)
        {
            sort(list);         //先排序
            Node lChild = list.get(list.size() - 1);    //列表里最小的元素
            Node rChild = list.get(list.size() - 2);  //第二小的元素
            
            Node parent = new Node("父节点  ",lChild.weight + rChild.weight);   //父节点
            
            parent.lChild = lChild;        //左孩子会比较小
            parent.rChild = rChild;
            
            list.remove(list.size()-1);   //删除俩元素
            list.remove(list.size()-1);   //减1后在减去1
            
            list.add(parent);
        }
        return list.get(0);
    }
    
    //广度优先遍历树,用于输出
    public  static List<Node>  deepFirst(Node root)
    {
        List<Node> list = new ArrayList<Node>();
        Queue<Node> queue = new ArrayDeque<Node>();    
        queue.add(root);
        
        while(!queue.isEmpty())
        {
            list.add(queue.peek()); //检索,但不删除此队列的头,或者返回null如果这个队列是空的。
            //此队列的头部,或null如果这个队列是空的
        
            Node x = queue.poll();   //检索并删除此队列的头,或者返回null如果这个队列是空的
            if(x.lChild!=null)
            {queue.add(x.lChild);}
            
            if(x.rChild!=null)
            {queue.add(x.rChild);}
        }
        return list;
    }
    public  static void main(String[] args) {  
             
        List<Node> list = new ArrayList<Node>();          //new一个新的列表存储各个节点
        
        Scanner cin = new Scanner(System.in);
        System.out.println("输入用于构造哈夫曼树的节点总数:");
        int n = cin.nextInt();
        System.out.println("输入各个节点以及他们的权值:");
        for(int i=0;i<n;i++)
        {
            String  c = cin.next();
            int x = cin.nextInt();
            Node<String> node = new Node<String>(c,x);
            list.add(node);
        }
    
        @SuppressWarnings("unchecked")
        Node<String> root = crateHufManTree(list);   //创建一棵哈夫曼树后取得节点
        System.out.println(deepFirst(root));          //广度优先遍历输出
    }      
}

 

测试结果:

3TY$S4BI0HZ[)EFXB4BUWB3

posted @ 2013-11-30 09:41  兰幽  阅读(777)  评论(0编辑  收藏  举报