精华内容
下载资源
问答
  • 构造最优二叉树-赫夫曼(Huffman)树算法  一、基本概念 1、赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数...

    http://blog.163.com/zhoumhan_0351/blog/static/3995422720098275836215/

    构造最优二叉树-赫夫曼(Huffman)树算法  

    一、基本概念

    1赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数为该结点的权。从树根结点到该结点之间的路径长度与该结点上权的乘积称为结点的带权路径长度(WPL),树中所有叶子结点的带权路径长度之和称为该树的带权路径长度,通常记为:





    2两结点间的路径:从一结点到另一结点所经过的结点序列;路径长度:从根结点到相应结点路径上的分支数目;树的路径长度:从根到每一结点的路径长度之和。

    3、深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1n的结点一一对应时,称为完全二叉树。在结点数目相同的二叉树中完全二叉树是路径长度最短的二叉树。

    4WPL最小的二叉树是最优二叉树(Huffman)

    5、赫夫曼(Huffman)树的特征

    当叶子上的权值均相同时,完全二叉树一定是最优二叉树。否则完全二叉树不一定是最优二叉树。

    在最优二叉树中,权值越大的叶子离根越近。

    最优二叉树的形态不唯一,但WPL最小。 

      






    如上图中,只有(d)才是赫夫曼树。其中,圆围中的数值代表权值。

    二、算法思想

      (1) 以权值分别为W1,W2...Wn的n各结点,构成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树Ti仅有一个权值为Wi的根结点;

      (2)F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值=Wi

      (3)F中删除这两棵二叉树,同时将新二叉树加入到F;

      (4)重复(2)(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。











    三、C语言描述

    (1)我们用如下结构来存储赫夫曼树:

          用大小为2n-1的一维数组来存储哈夫曼树中的结点,其存储结构为:

    #definen 100              //叶结点数目

    #definem 2*n-1            //树中结点总数

    typedefstruct

    {float weight;            //权值,设权值均大于零

    intlchild,rchild,parent;  //左右孩子及双亲指针

    }HTNode;

      typedefHTNode HuffmanTree[m]; //哈夫曼树是一维数组

         因为C语言数组的下界为0,用-1表示空指针。树中结点的lchildrchildparent不等于-1时,分别表示该结点的左、右孩子和双亲结点在数组中的下标。

    设置parent域有两个作用:一是使查找某结点的双亲变得简单;二是可通过判定parent的值是否为-1来区分根与非根结点。

    (2)哈夫曼算法的简要描述

    在上述存储结构上实现的哈夫曼算法可大致描述为(T的类型为HuffmanTree)

    初始化T[0m-1]2n-1个结点里的三个指针均置为空(即置为-1),权值置为0

    输入读入n个叶子的权值存于数组的前n个分量(T[0n-1])中。它们是初始森林中n个孤立的根结点上的权值。

    合并对森林中的树共进行n-1次合并,所产生的新结点依次放入数组T的第i个分量中(nim-1)。每次合并分两步:

    1)在当前森林T[0i-1]的所有结点中,选取权值最小和次小的两个根结点T[p1]T[p2]作为合并对象,这里0p1p2i-1

           2)将根为T[p1]T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]

    具体操作:

    T[p1]T[p2]parent置为i

    T[i]lchildrchild分别置为p1p2

    新结点T[i]的权值置为T[p1]T[p2]的权值之和。

    合并后T[pl]T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。

    (3)赫夫曼算法的数组法构造

    voidCreateHuffmanTree(HuffmanTree T)

    {int i,p1,p2;                       //构造哈夫曼树,T[m-1]为其根结点

    InitHuffmanTree(T);      //T初始化

    InputWeight(T);              //输入叶子权值至T[0..n-1]weight

      for(i=n;i<m;i++)

    { SelectMin(T,i-1,&p1,&p2);//共进行n-1次合并,新结点依次存于T[i]

               //T[0…i-1]中选择两个权最小的根结点,其序号分别为p1p2

      T[p1].parent=T[p2].parent=i;

      T[i].1child=p1;             //最小权的根结点是新结点的左孩子

      T[i].rchild=p2;              //次小权的根结点是新结点的右孩子

     T[i].weight=T[p1].weight+T[p2].weight;

     }//for

    }//CreateHuffman

    四、C语言实现

    #include"stdio.h"

    #include"stdlib.h"

    #definem 100

     

    structptree             //定义二叉树结点类型

    {

    intw;                  //定义结点权值

    structptree *lchild;     //定义左子结点指针

    structptree *rchild;     //定义右子结点指针

    };

     

    structpforest             //定义链表结点类型

    {

    structpforest *link;

    structptree *root;

    };

     

    intWPL=0;                 //初始化WTL0

    structptree *hafm();

    voidtravel();

    structpforest *inforest(struct pforest *f,struct ptree *t);

     

    voidtravel(struct ptree *head,int n)                     

    {

    //为验证harfm算法的正确性进行的遍历

    structptree *p;

    p=head;

    if(p!=NULL)

     {

          if((p->lchild)==NULL&& (p->rchild)==NULL) //如果是叶子结点

         {

                 printf("%d",p->w);

                 printf("thehops of the node is: %d\n",n);

          WPL=WPL+n*(p->w);   //计算权值

            }//if

    travel(p->lchild,n+1);

    travel(p->rchild,n+1);

    }//if

    }//travel

     

    structptree *hafm(int n, int w[m])

    {

    structpforest *p1,*p2,*f;

    structptree *ti,*t,*t1,*t2;

    inti;

    f=(pforest*)malloc(sizeof(pforest));

    f->link=NULL;

    for(i=1;i<=n;i++)         //产生n棵只有根结点的二叉树

      {

          ti=(ptree*)malloc(sizeof(ptree));//开辟新的结点空间

       ti->w=w[i];              //给结点赋权值

       ti->lchild=NULL;

       ti->rchild=NULL;

       f=inforest(f,ti);

          //按权值从小到大的顺序将结点从上到下地挂在一颗树上      

      }//for

    while(((f->link)->link)!=NULL)//至少有二棵二叉树

      {

     p1=f->link;

     p2=p1->link;

     f->link=p2->link;          //取出前两棵树

     t1=p1->root;

     t2=p2->root;

     free(p1);                //释放p1

     free(p2);                //释放p2

      t=(ptree*)malloc(sizeof(ptree));//开辟新的结点空间

      t->w= (t1->w)+(t2->w);        //权相加

     t->lchild=t1;

     t->rchild=t2;            //产生新二叉树

     f=inforest(f,t);

      }//while

     p1=f->link;

     t=p1->root;

      free(f);

     return(t);                 //返回t

     }

     

    pforest*inforest(struct pforest *f,struct ptree *t)

    {

    //按权值从小到大的顺序将结点从上到下地挂在一颗树上      

    structpforest *p, *q, *r;

    structptree *ti;

    r=(pforest*)malloc(sizeof(pforest)); //开辟新的结点空间

    r->root=t;

    q=f;

    p=f->link;              

    while(p!=NULL)           //寻找插入位置

     {

      ti=p->root;

      if(t->w> ti->w)         //如果t的权值大于ti的权值

        {

            q=p;

         p=p->link;            //p向后寻找

        }//if

      else

         p=NULL;                 //强迫退出循环

      }//while

    r->link=q->link;

    q->link=r;                //r接在q的后面

    return(f);                //返回f

    }

     

    voidInPut(int &n,int w[m])

    {

    printf("pleaseinput the sum of node\n"); //提示输入结点数

    scanf("%d",&n);     //输入结点数

    printf("please input weight of every node\n"); //提示输入每个结点的权值

    for(inti=1;i<=n;i++)

    scanf("%d",&w[i]); //输入每个结点权值

    }

     

    intmain( )

    {

    structptree *head;

    intn,w[m];

    InPut(n,w);

    head=hafm(n,w);

    travel(head,0);

    printf("Thelength of the best path is WPL=%d", WPL);//输出最佳路径权值之和

    return1;

    }

    五、最优二叉树的应用

    哈夫曼树的应用很广,哈夫曼编码就是哈夫曼树在电讯通信中的应用之一。它采用不等长编码,让出现次数多的字符用短码,且任一编码不能是另一编码的前缀(我们称之为前缀编码,或非前缀码)。设有n种字符,每种字符出现的次数为Wi,其编码长度为Li(i=1,2,...n),则整个电文总长度为∑WiLi ,要得到最短的电文,即使得∑WiLi最小。也就是以字符出现的次数为权值,构造一棵Huffman树,并规定左分支编码位0,右分支编码为1,则字符的编码就是从根到该字符所在的叶结点的路径上的分支编号序列。用构造Huffman树编出来的码,称为Huffman编码。

    为了获得传送电文的最短长度,可将字符出现的次数(频率)作为权值赋予该结点,构造一棵WPL最小的哈夫曼树,由此得到的二进制前缀编码就是最优前缀编码,也称哈夫曼编码。可以验证,用这样的编码传送电文可使总长最短。如图所示:



         




    我们在修改程序时,只要将原来的travel改成如下即可实现赫夫曼编码,当然,译码原理也相同:
    #definecount 15
    char ch[count][count];
    int counter=0;
    inttag,counter2=0;
    void Initch()
    {
    for(int i=0;i<count;i++)
       ch[i][count-1]='\0';
    }

    voidtravel(struct ptree *head,int n,int tag)                     
    {
    //
    为验证harfm算法的正确性进行的遍历
    structptree *p;
    p=head;
    if(p!=NULL)
     {
       if(tag==-1)
            {
           ch[counter][counter2++]='0';
           //for(int j=counter;j<count-1;j++)strcpy(ch[j+1],ch[counter]);
           }
        if(tag==+1)
           {
           ch[counter][counter2++]='1';
           for(int j=counter;j<count-1;j++) strcpy(ch[j+1],ch[counter]);
           }
        if((p->lchild)==NULL &&(p->rchild)==NULL) //
    如果是叶子结点
         {
            printf("%d",p->w);
            printf("thehops of the node is: %d ",n);
           printf("Code is %s\n",ch[counter]);
           counter++;
           counter2--;
           WPL=WPL+n*(p->w);    //
    计算权值
        }//if
    travel(p->lchild,n+1,-1);
    travel(p->rchild,n+1,+1);
    }//if
    }//travel



    展开全文
  • Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)为什么哈夫曼编码能够实现文件的压缩如果我们...

    什么是哈夫曼编码

    哈夫曼编码(Huffman Coding),又称霍夫曼编码,是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)

    为什么哈夫曼编码能够实现文件的压缩

    如果我们使用的定长编码方例如ASCII码,8-bit定长编码,使用8位(一个字节)代表一个字符,就会出现很多的byte出现了相同,所以我们考虑一种新的编码方式,变长编码的方式,原理很简单,以前是使用的8位表示一个字符,现在该用不固定长度,确定长度的依据是每个字符出现的次数,越是高频的字符其所用的bit(二进制位数)越短,这样就实现了整个文本的变短,

    //举例我们有这么一个字符串:

    String content = "abbcccddddeeeee";

    当我们把这段文字发送给别人时,会转换成一段字节数组长成下面这个样子

    字节数组:

    [97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101]

    二进制:

    110000111000101100010110001111000111100011110010011001001100100110010011001011100101110010111001011100101

    使用哈夫曼编码过后

    先统计文本每个字符的 a : 1 b:2 c:3 d:4 e:5

    每个字符对应的二进制 a->1110 b->1111 c->110 d->10 e->0

    二进制:

    010011011000000101010101111111111

    字节数组:

    [77, -127, 85, -1, 1]

    要想学习哈夫曼编码先了解哈夫曼树的基本概念

    路径和路径长度:在一棵树中,从节点往下可以达到的孩子或者孙子节点之间的通路,从树根节点到叶子节点的长度称为路径长度,从从根节点到第L层节点到路径长度为L-1

    节点的权及带权路径长度:给节点值我们就叫做节点的权值,节点带权路径的为: 路径长度 * 权值

    一整个树的带权路径长度会等于所有叶子节点的带权路径长度之和,称为WPL(weighted path length)

    WPL最小时就叫做哈夫曼树,因此我们构建哈夫曼树时应该将最大权值的节点放在根节点附近

    a5975823b365

    非最优二叉树1.png

    a5975823b365

    非最优二叉树2

    a5975823b365

    哈夫曼树.png

    1、压缩算法实现部分

    基本思路

    构造树节点node

    用生成的节点node构造哈夫曼树

    生成哈夫曼编码表

    用哈夫曼编码表生成压缩后的字节数组

    构造树节点node

    这是node类实现了Comparable借口,并且重写了compareTo可以后续用于所有node根据权值排序

    public class Node implements Comparable{

    Byte data; //存放数据本身

    int weight; //权值

    Node left; //指向左子节点

    Node right; //指向右子节点

    public Node(Byte data, int weight) {

    this.data = data;

    this.weight = weight;

    }

    @Override

    public int compareTo(Node o) {

    // TODO Auto-generated method stub

    return this.weight-o.weight;

    }

    @Override

    public String toString() {

    return "Node [data=" + data + ", weight=" + weight + "]";

    }

    }

    根据文本信息转换成Byte数组,再创建一个HashMap 遍历整个byte数组key保存当前的byte的值,value用于统计相同字节出现的频率,再把这个HashMap中的节点转换成Node节点

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    String content = "abbcccddddeeeee";

    byte[] contentBytes = content.getBytes();

    List nodes = getNodes(bytes);

    }

    private static List getNodes(byte[] bytes) {

    // 创建Arrlist

    ArrayList nodes = new ArrayList();

    // 遍历bytes,统计byte出现的次数->map

    Map counts = new HashMap();

    for (byte b : bytes) {

    if (!counts.containsKey(b)) {

    counts.put(b, 1);

    } else {

    counts.put(b, counts.get(b) + 1);

    }

    }

    // 把每一个键对转成一个Node对象,并加入到nodes集合中

    for (Map.Entry entry : counts.entrySet()) {

    nodes.add(new Node(entry.getKey(), entry.getValue()));

    }

    return nodes;

    }

    用生成的节点node构造哈夫曼树

    权值越大,带权路径长度应该最短越接近root节点,原因是用0和1分别表示二叉树的左右字节点可达路径,

    对传入的list中的node从小到大进行排序,拿出第一个最小的两个元素,构成新的节点放入list中

    对新的节点不赋值,权重是两个,把它的左右节点初始化为最小的两个元素,权重是两个是两个子节点的和

    重复以上的步骤直到list中的元素小2时候停止

    示例 String content = "abbcccddddeeeee";

    a : 1 b:2 c:3 d:4 e:5

    a5975823b365

    步骤1

    a5975823b365

    步骤2

    a5975823b365

    步骤3

    a5975823b365

    步骤4

    具体代码如下:

    // 可以通过list创建哈夫曼树

    /**

    * @param nodes 要处理的nodes

    * @return 返回root节点

    */

    public static Node createHuffmanTree(List nodes) {

    // 传入的list中大于1个节点时候才构建二叉树

    while (nodes.size() > 1) {

    // 对集合里面的元素进行排序

    Collections.sort(nodes);

    // 取出权值最小的节点

    Node left = nodes.get(0);

    Node right = nodes.get(1);

    // 构建一个新的二叉树节点,并且初始化它的值为null

    Node parent = new Node(null, left.weight + right.weight);

    // 把两个节点挂在一个非子节点上

    parent.left = left;

    parent.right = right;

    // 移除左右两个节点

    nodes.remove(left);

    nodes.remove(right);

    // 把这个非叶子节点加入到存放到nodes中

    nodes.add(parent);

    }

    return nodes.get(0);

    }

    构建哈夫曼树的输出结果

    Node [data=null, weight=15]

    Node [data=null, weight=6]

    Node [data=99, weight=3]

    Node [data=null, weight=3]

    Node [data=97, weight=1]

    Node [data=98, weight=2]

    Node [data=null, weight=9]

    Node [data=100, weight=4]

    Node [data=101, weight=5]

    生成哈夫曼编码表

    首先要创建huffmanCodes哈夫曼编码表 ,表的key - node.data(a,b,c) value - 0101,重载getCodes函数定义一个StringBuilder用于字符串010101的连接,递归处理之前生成好的哈夫曼树,往左走拼接 '0' ,往右走拼接'1'只有当我们遍历到叶子节点才把它加入huffmanCodes中

    // 声明哈夫曼编码表,static修饰,在多个方法中共同使用

    public static Map huffmanCodes = new HashMap();

    // 生成哈夫曼树对应的哈夫曼编码

    // 思路:

    // 将哈夫曼编码存放在map中比较合适 Map

    // e->0 d->10 c->110 b->1111 a->1110

    // 为了调用方便,重载getCodes

    private static Map getCodes(Node root) {

    if (root == null) {

    return null;

    }

    //处理只有root节点的特殊情况

    if (root.left == null && root.right == null) {

    huffmanCodes.put(root.data, "0");

    }

    // 在生成哈夫曼编码表时,需要去拼接路径,定义一个StringBuilder 存储叶子节点的路径

    StringBuilder builder = new StringBuilder();

    // 处理左子树

    getCodes(root.left, "0", builder);

    // 处理右子树

    getCodes(root.right, "1", builder);

    return huffmanCodes;

    }

    /**

    * 功能:将传入的node节点的所有叶子节点的哈夫曼得到,并放入huffmanCodes中

    *

    * @param node //传入节点

    * @param code //路径:左子节点是0 右子节点1

    * @param builder //用于拼接路径

    */

    private static void getCodes(Node node, String code, StringBuilder builder) {

    // 重建StringBuilder是为了防治地址引用一值,保持生成字符不共用同一字符串

    StringBuilder builder2 = new StringBuilder(builder);

    // 将code加入到StringBuilder

    builder2.append(code);

    if (node != null) { // 如果为空不进行处理

    // 判断当前是叶子还是非叶子节点

    if (node.data == null) {

    // 递归处理

    // 左边递归

    getCodes(node.left, "0", builder2);

    // 右边递归

    getCodes(node.right, "1", builder2);

    } else {

    // 找到了叶子节点

    huffmanCodes.put(node.data, builder2.toString());

    }

    }

    }

    用哈夫曼编码表生成压缩后的字节数组

    最后一步就是用我们生成好的哈夫曼编码表对我们的全部文本进行生成处理过后的字节数组,遍历整个字节数组用字节数组的每个数据去hashmap中匹配010101生成二进制字符串,因为哈夫曼编码表对应的二进制是可变长的,所以最后字符串的长度可能就不是8的倍数,要进行特殊处理多加一个字节,最后一个字节如果不满8bit,我们在这里要用一个static int endLen记录下它的长度,在后面逆向解压中将要用到 , 再遍历整个字符串数组用Byte数组来封装好整个字符串,1Byte=8bit, 具体的代码如下:

    // 编写一个方法,将字符串对应的byte[]数组,通过生成哈夫曼编码表,返回一个哈夫曼编码处理后的byte[]数组

    /**

    * @param bytes 原始数据要处理的byte数组

    * @param huffmanCode //哈夫曼编码表

    * @return //返回处理后的byte[]

    */

    public static byte[] zip(byte[] bytes, Map huffmanCode) {

    // 利用huffmanCodes将byte转成哈夫曼对应的字符串

    StringBuilder builder = new StringBuilder("");

    // 遍历数组

    for (byte b : bytes) {

    builder.append(huffmanCodes.get(b));

    }

    // 将"10101000..." 转成byte[]

    // 统计返回的哈夫曼编码有多长

    int len = (builder.length() % 8) == 0 ? builder.length() / 8 : builder.length() / 8 + 1;

    endLen = builder.length() % 8;

    // 创建 存储后的bytes压缩数组

    byte[] huffmanCodeBytes = new byte[len];

    int index = 0;// 记录是第几个byte

    for (int i = 0; i < builder.length(); i += 8) {// 因为是每8位对应一个byte,所以步长+8

    String strByte;

    // 两种情况i+8超过最后位置和不超过的分别赋值

    strByte = i + 8 > builder.length() ? builder.substring(i) : builder.substring(i, i + 8);

    // 后面一个参数2表示转换成二进制

    huffmanCodeBytes[index++] = (byte) Integer.parseInt(strByte, 2);

    }

    return huffmanCodeBytes;

    }

    处理之后的结果

    处理前的字节数组:[97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101] 长度=15

    处理后的字节数组:[77, -127, 85, -1, 1] 长度=5

    当重复字符较多时,压缩效率还是相当可观的

    对整个压缩流程创建一个接口函数方便调用

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    String content = "abbcccddddeeeee";

    byte[] contentBytes = content.getBytes();

    byte[] huffmanCodeBytes = huffmanZip(contentBytes);

    }

    // 创建一个接口函数封装好实现的细节

    /**

    *

    * @param bytes 原始字符串对应的字节数组

    * @return 返回处理后的字节数组

    */

    public static byte[] huffmanZip(byte[] bytes) {

    System.out.println("处理前的字节数组:"+Arrays.toString(bytes)+" 长度="+bytes.length);

    List nodes = getNodes(bytes);

    // 创建哈夫曼树

    Node huffmanTreeRoot = createHuffmanTree(nodes);

    // 对应的哈夫曼编码

    Map huffmanCodes = getCodes(huffmanTreeRoot);

    // 根据生成的哈夫曼编码,得到压缩后的数组

    byte[] huffmanCodeBytes = zip(bytes, huffmanCodes);

    System.out.println("处理后的字节数组:"+Arrays.toString(huffmanCodeBytes)+" 长度="+huffmanCodeBytes.length);

    preOrder(huffmanTreeRoot);

    return huffmanCodeBytes;

    }

    2、解压算法实现部分

    基本思路

    字节数组转换成二进制字符串

    逆向处理生成好的哈夫曼编码表

    根据逆向生成的哈夫曼表查询生成原来的字节数组

    字节数组转换成二进制字符串

    思路就是用flag来标识是否时最后一位,通过之前的endLen得到最后一位如果不满八位就进行特殊处理

    代码处理主要用二进制的操作,byte的取值范围为-128-127,如果出现负数则要进行补高位

    // 完成数据转成对应的二进制字符串'10101000...'

    /**

    * 将一个byte 转成一个二进制的字符串

    *

    * @param b 传入的是一个字节b

    * @param flag 标志是否为最后一个字节,true表示不是最后一个字节,false表示是最后一个字节

    * @return 是该b对应对二进制对字符串,(注意是按照补码返回)

    */

    public static String byteToBitString(boolean flag, byte b) {

    // 使用一个变量保持b

    int temp = b; // 将b转成int

    temp |= 256;

    String str = Integer.toBinaryString(temp);// 返回的是temp对应的二进制补码

    if (flag || (flag == false && endLen == 0)) {

    //字符串的截取,只拿后八位

    return str.substring(str.length() - 8);

    } else {

    //不满8bit有多少位拿多少位

    return str.substring(str.length() - endLen);

    }

    逆向处理生成好的哈夫曼编码表

    用循环把编码表key(a,b,c)-value(0101) 倒转过来

    // 把哈夫曼编码表进行调换,因为要进行反向查询

    Map map = new HashMap();

    for (Map.Entry entry : huffmanCodes.entrySet()) {

    map.put(entry.getValue(), entry.getKey());

    }

    根据逆向生成的哈夫曼表查询生成原来的字节数组

    取生成好的010101二进制字符串,根据哈夫曼编码表查出原来的每个字节,采用双指针移动的方式去hashmap中查询,查到了对应的字节,吧它加入到我们的list中,再把list转换成byte数组进行返回

    // 编写一个方法,完成对压缩数据对解码

    /**

    * @param huffmanCodes 哈夫曼编码表

    * @param huffmanBytes 哈夫曼编码得到对字节数组

    * @return 就是原来对字符串对应对数组

    */

    public static byte[] decode(Map huffmanCodes, byte[] huffmanBytes) {

    // 1.先得到 huffmanBytes 对应对二进制字符串,形式10101000...

    StringBuilder builder = new StringBuilder();

    // 2.将byte数组转成二进制的字符串

    for (int i = 0; i < huffmanBytes.length; i++) {

    byte b = huffmanBytes[i];

    // 判断是不是最后一个字节

    boolean flag = (i == huffmanBytes.length - 1);

    builder.append(byteToBitString(!flag, b));

    }

    // 创建集合,存放byte

    List list = new ArrayList<>();

    for (int i = 0; i < builder.length();) {

    int count = 1; // 小的计数器

    boolean flag = true;

    Byte b = null;

    while (flag) {

    // 取出一个bit '1'或者'0'

    String key = builder.substring(i, i + count); // i 不动 让count移动,直到匹配到一个字符

    b = map.get(key);

    if (b == null) {// 没有匹配到

    count++;

    } else {

    flag = false;

    }

    }

    list.add(b);

    i = i + count;

    }

    // 当for循环结束以后,list中存放了所有当字符

    // 把list中的数据放入到byte[] 并返回

    byte b[] = new byte[list.size()];

    for (int i = 0; i < b.length; i++) {

    b[i] = list.get(i);

    }

    return b;

    }

    所有调用过程main函数

    public static void main(String[] args) {

    // TODO Auto-generated method stub

    String content = "abbcccddddeeeee";

    byte[] contentBytes = content.getBytes();

    byte[] huffmanCodeBytes = huffmanZip(contentBytes);

    byte[] source = decode(huffmanCodes, huffmanCodeBytes);

    System.out.println("原来的字符串=" + new String(source));

    }

    压缩-解压的处理结果

    处理前的字节数组:[97, 98, 98, 99, 99, 99, 100, 100, 100, 100, 101, 101, 101, 101, 101] 长度=15

    处理后的字节数组:[77, -127, 85, -1, 1] 长度=5

    原来的字符串=abbcccddddeeeee

    总结

    以上就就是关于所有哈java实现夫曼编码的所有细节,希望你对此有更清晰的的了解

    完整源代码移步github

    声明

    1、如果在文章当中发现有描述错误的地方,还请您不吝指出,万分感谢!

    2、此文章系本人原创作品,转发请注明出处!

    展开全文
  • 步骤二:讲这两个元素作为树的左右子树,构造新的二叉树, 步骤三:将这两个元素从之前的集合中删除,并将得到的新的二叉树的根节点的权值放到二叉树中 步骤四:重复以上步骤,直到集合F’中只有一个元素为止 ...

    在这里插入图片描述
    核心:
    步骤一:取F集合中最小的两个元素。
    步骤二:讲这两个元素作为树的左右子树,构造新的二叉树,
    步骤三:将这两个元素从之前的集合中删除,并将得到的新的二叉树的根节点的权值放到二叉树中
    步骤四:重复以上步骤,直到集合F’中只有一个元素为止

    展开全文
  • 1、赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数为该结点的权。从树根结点到该结点之间的路径长度与该结点上权...

    一、基本概念

    1、赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数为该结点的权。从树根结点到该结点之间的路径长度与该结点上权的乘积称为结点的带权路径长度(WPL),树中所有叶子结点的带权路径长度之和称为该树的带权路径长度,通常记为:

    2、两结点间的路径:从一结点到另一结点所经过的结点序列;路径长度:从根结点到相应结点路径上的分支数目;树的路径长度:从根到每一结点的路径长度之和。

    3、深度为k,结点数为n的二叉树,当且仅当每个结点的编号都与相同深度的满二叉树中从1到n的结点一一对应时,称为完全二叉树。在结点数目相同的二叉树中完全二叉树是路径长度最短的二叉树。

    4、WPL最小的二叉树是最优二叉树(Huffman 树)。

    5、赫夫曼(Huffman)树的特征

    ① 当叶子上的权值均相同时,完全二叉树一定是最优二叉树。否则完全二叉树不一定是最优二叉树。

    ② 在最优二叉树中,权值越大的叶子离根越近。

    ③ 最优二叉树的形态不唯一,但WPL最小。  

       

    如上图中,只有(d)才是赫夫曼树。其中,圆围中的数值代表权值。

    二、算法思想 

     (1)  以权值分别为W1,W2...Wn的n各结点,构成n棵二叉树T1,T2,...Tn并组成森林F={T1,T2,...Tn},其中每棵二叉树 Ti仅有一个权值为 Wi的根结点;

      (2)在F中选取两棵根结点权值最小的树作为左右子树构造一棵新二叉树,并且置新二叉树根结点权值为左右子树上根结点的权值之和(根结点的权值=左右孩子权值之和,叶结点的权值= Wi)

      (3)从F中删除这两棵二叉树,同时将新二叉树加入到F中;

      (4)重复(2)、(3)直到F中只含一棵二叉树为止,这棵二叉树就是Huffman树。

    三、C语言描述

    (1)我们用如下结构来存储赫夫曼树:

           用大小为2n-1的一维数组来存储哈夫曼树中的结点,其存储结构为:

    #define n 100               //叶结点数目

    #define m 2*n-1             //树中结点总数

    typedef struct 

    { float weight;             //权值,设权值均大于零

    int lchild,rchild,parent;  //左右孩子及双亲指针

    } HTNode;

      typedef HTNode HuffmanTree[m]; //哈夫曼树是一维数组

          因为C语言数组的下界为0,用-1表示空指针。树中结点的lchild、rchild和parent不等于-1时,分别表示该结点的左、右孩子和双亲结点在数组中的下标。

    设置parent域有两个作用:一是使查找某结点的双亲变得简单;二是可通过判定parent的值是否为-1来区分根与非根结点。

    (2)哈夫曼算法的简要描述

    在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):

    ①初始化 将T[0…m-1]中2n-1个结点里的三个指针均置为空(即置为-1),权值置为0。

    ②输入 读入n个叶子的权值存于数组的前n个分量(即T[0…n-1])中。它们是初始森林中n个孤立的根结点上的权值。

    ③合并 对森林中的树共进行n-1次合并,所产生的新结点依次放入数组T的第i个分量中(n≤i≤m-1)。每次合并分两步:

    1) 在当前森林T[0…i-1]的所有结点中,选取权值最小和次小的两个根结点T [p1]和T[p2]作为合并对象,这里0≤p1,p2≤i-1。

           2) 将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。 

    具体操作:

    将T[p1]和T[p2]的parent置为i;

    将T[i]的lchild和rchild分别置为p1和p2;

    新结点T[i]的权值置为T[p1]和T[p2]的权值之和。

    合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。

    (3)赫夫曼算法的数组法构造

    void CreateHuffmanTree(HuffmanTree T)

    { int i,p1,p2;                        //构造哈夫曼树,T[m-1]为其根结点

    InitHuffmanTree(T);       //T初始化

    InputWeight(T);               //输入叶子权值至T[0..n-1]weight

     for(i=n;i<m;i++)

    { SelectMin(T,i-1,&p1,&p2);//共进行n-1次合并,新结点依次存于T[i]

               //T[0…i-1]中选择两个权最小的根结点,其序号分别为p1p2

     T[p1].parent=T[p2].parent=i;

     T[i].1child=p1;              //最小权的根结点是新结点的左孩子

     T[i].rchild=p2;               //次小权的根结点是新结点的右孩子

     T[i].weight=T[p1].weight+T[p2].weight;

     }//for

    }//CreateHuffman 

    四、C语言实现

    #include "stdio.h"

    #include "stdlib.h"

    #define m 100

     

    struct ptree              //定义二叉树结点类型

    int w;                   //定义结点权值

    struct ptree *lchild;     //定义左子结点指针

    struct ptree *rchild;     //定义右子结点指针

    };

     

    struct pforest              //定义链表结点类型

    struct pforest *link; 

    struct ptree *root; 

    }; 

     

    int WPL=0;                  //初始化WTL为0

    struct ptree *hafm();

    void travel();

    struct pforest *inforest(structpforest *f,struct ptree *t); 

     

    void travel(struct ptree*head,int n)                      

    {

    //为验证harfm算法的正确性进行的遍历

    struct ptree *p; 

    p=head;

    if(p!=NULL)

     {

           if((p->lchild)==NULL && (p->rchild)==NULL) //如果是叶子结点

          { 

                  printf("%d ",p->w);

                  printf("the hops of the node is: %d\n",n);

           WPL=WPL+n*(p->w);    //计算权值

             }//if

    travel(p->lchild,n+1);

    travel(p->rchild,n+1);

    }//if

    }//travel 

     

    struct ptree *hafm(int n, intw[m])

    struct pforest *p1,*p2,*f;

    struct ptree *ti,*t,*t1,*t2;

    int i;

    f=(pforest*)malloc(sizeof(pforest));

    f->link=NULL;

    for(i=1;i<=n;i++)          //产生n棵只有根结点的二叉树

      { 

           ti=(ptree*)malloc(sizeof(ptree));//开辟新的结点空间

        ti->w=w[i];               //给结点赋权值

        ti->lchild=NULL; 

        ti->rchild=NULL;

        f=inforest(f, ti);

           //按权值从小到大的顺序将结点从上到下地挂在一颗树上       

      }//for

    while(((f->link)->link)!=NULL)//至少有二棵二叉树

      { 

      p1=f->link;

      p2=p1->link;

      f->link=p2->link;           //取出前两棵树

      t1=p1->root;

      t2=p2->root;

      free(p1);                //释放p1

      free(p2);                 //释放p2

      t=(ptree *)malloc(sizeof(ptree));//开辟新的结点空间

      t->w = (t1->w)+(t2->w);         //权相加

      t->lchild=t1;

      t->rchild=t2;             //产生新二叉树

      f=inforest(f,t);

      }//while

      p1=f->link; 

      t=p1->root;

      free(f);

     return(t);                  //返回t

     }

     

    pforest *inforest(structpforest *f,struct ptree *t)

    //按权值从小到大的顺序将结点从上到下地挂在一颗树上       

    struct pforest *p, *q, *r;

    struct ptree *ti;

    r=(pforest*)malloc(sizeof(pforest)); //开辟新的结点空间

    r->root=t; 

    q=f; 

    p=f->link;               

    while (p!=NULL)            //寻找插入位置

     { 

       ti=p->root; 

       if(t->w > ti->w)          //如果t的权值大于ti的权值

         { 

             q=p;

          p=p->link;             //p向后寻找

         }//if

       else

          p=NULL;                  //强迫退出循环

      }//while

    r->link=q->link; 

    q->link=r;                 //r接在q的后面

    return(f);                 //返回f

    }

     

    void InPut(int &n,int w[m])

    {

    printf("please input thesum of node\n"); //提示输入结点数

    scanf("%d",&n);      //输入结点数

    printf ("please inputweight of every node\n"); //提示输入每个结点的权值

    for(int i=1;i<=n;i++)

    scanf("%d",&w[i]);  //输入每个结点权值

    }

     

    int main( )

    struct ptree *head;

    int n,w[m]; 

    InPut(n,w);

    head=hafm(n,w); 

    travel(head,0); 

    printf("The length of thebest path is WPL=%d", WPL);//输出最佳路径权值之和

    return 1;

    }

    五、最优二叉树的应用

    哈夫曼树的应用很广,哈夫曼编码就是哈夫曼树在电讯通信中的应用之一。它采用不等长编码,让出现次数多的字符用短码,且任一编码不能是另一编码的前缀(我们称之为前缀编码,或非前缀码)。设有n种字符,每种字符出现的次数为Wi,其编码长度为Li(i=1,2,...n),则整个电文总长度为∑Wi Li ,要得到最短的电文,即使得∑ Wi Li最小。也就是以字符出现的次数为权值,构造一棵 Huffman树,并规定左分支编码位0,右分支编码为1,则字符的编码就是从根到该字符所在的叶结点的路径上的分支编号序列。用构造Huffman树编出来的码,称为Huffman编码。

    为了获得传送电文的最短长度,可将字符出现的次数(频率)作为权值赋予该结点,构造一棵WPL最小的哈夫曼树,由此得到的二进制前缀编码就是最优前缀编码,也称哈夫曼编码。可以验证,用这样的编码传送电文可使总长最短。如图所示:

           我们在修改程序时,只要将原来的travel改成如下即可实现赫夫曼编码,当然,译码原理也相同:
    #define count 15
    char ch[count][count];
    int counter=0;
    int tag,counter2=0;
    void Initch()
    {
    for(int i=0;i<count;i++)
        ch[i][count-1]='\0';
    }

    void travel(struct ptree *head,int n,int tag)                      
    {
    //为验证harfm算法的正确性进行的遍历
    struct ptree *p; 
    p=head;
    if(p!=NULL)
     {
        if(tag==-1) 
            {
            ch[counter][counter2++]='0';
            //for(int j=counter;j<count-1;j++) strcpy(ch[j+1],ch[counter]);
            }
        if(tag==+1) 
            {
            ch[counter][counter2++]='1';
            for(int j=counter;j<count-1;j++) strcpy(ch[j+1],ch[counter]);
            }
        if((p->lchild)==NULL && (p->rchild)==NULL) //如果是叶子结点
          { 
            printf("%d ",p->w);
            printf("the hops of the node is: %d ",n);
            printf("Code is %s\n",ch[counter]);
            counter++;
            counter2--;
            WPL=WPL+n*(p->w);    //计算权值
          }//if
    travel(p->lchild,n+1,-1);
    travel(p->rchild,n+1,+1);
    }//if
    }//travel 

    展开全文
  • 1、赫夫曼(Huffman)树又称最优二叉树或最优搜索树,是一种带权路径长度最短的二叉树。在许多应用中,常常赋给树中结点一个有某种意义的实数,称此实数为该结点的权。从树根结点到该结点之间的路径长度与该结点上权...
  • 构造二叉树 最优二叉树 树 输出二叉树到屏幕 C# .net
  • 对于给定一个长度为m序列,构造一颗以序列值为权的m个外部结点的扩充二叉树,使得带权的外部路径长度WPL最小,就称这颗扩充二叉树为 哈夫曼(Huffman)树(最优二叉树)。构造Huffman Tree 的算法也就是哈夫曼算法。...
  • 最优二叉树是什么? 概述:又称为哈夫曼树或者霍夫曼树,它是一类带权路径长度最短的树。路径是从树中一个结点到另一个结点之间的通路,路径上的分支数目称为路径长度。...构造最优二叉树的哈夫曼算法如下:
  • 最优二叉树

    2021-05-25 11:20:22
    给定n个权值作为n的[叶子]结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 思路 将输入的...
  • 然后构造最优二叉树的过程就是,每次从二叉树数组中选取权值最小的二叉树,然后将这两个二叉树作为左子树和右子树,构造一个新的二叉树,双亲结点的权值就是它俩的权值之和。 我们递归执行这一过程,当二叉树数组...
  • 最优二叉树算法

    千次阅读 2013-05-15 20:00:22
    练习做一下构造最优二叉树的算法,不过如何计算WPL呢? 本次未能实现,希望懂的人可以帮我解决一下这个问题,不胜感激! // // 头文件:HuffmanTree.h // // 叶子结点的最大数量 #define LEAVES_COUNT 4 // // ...
  • 原理来自于《算法导论》,其实和矩阵的动态规划基本一样,所以这里就不作阐述了。直接上代码,通过构造了最优的root数组后,很容易再...最后解释最优二叉树时,需要把C语言形式的root数组转换为原来书上的数组格式...
  • 构造最优二叉树的方法如下: (1) 将每个带有权值的结点作为一棵仅有根结点的二叉树,树的权值为结点的权值。 (2) 将其中两棵权值最小的树组成一棵新的二叉树,新树的权值为两棵树的权值之和。 (3)
  • 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树。 哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 二,哈夫曼编码 以下...
  • 经教主的指导+自己的研究,终于搞懂这道题了。。。 赫夫曼树(最优二叉树) ...构造 最优二叉树,求树的带全路径长度 #include #include #include #include using namespace std; int main() { int i,j,k;
  • 最优二叉树-哈夫曼树

    2021-04-11 00:14:50
    给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。 哈夫曼树又...
  • 最优二叉树是带权路径长度最短的二叉树...构造最优二叉树的方法 如下: (1) 将每个带有权值的结点作为一棵仅有根结点的二叉树,树的权值为结点的权值; (2) 将其中两棵权值最小的树组成一棵新二叉树,新树的权值为两
  • 【哈夫曼树(最优二叉树)】 --- 概念以及构造哈夫曼树产生的背景准备概念哈夫曼二叉树的概念以及构造 哈夫曼树产生的背景 准备概念 哈夫曼二叉树的概念以及构造 ...
  • (1.2.5.7)哈弗曼树=最优二叉树

    千次阅读 2015-03-14 15:15:20
    哈弗曼依据这一特点提出了一种构造最优二叉树的方法,其基本思想如下:   下面演示了用Huffman算法构造一棵Huffman树的过程: (2)例题: 假设一个文本文件TFile中只包含7个字符{A,B,C,D,...
  • 哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。 如何计算带权路径长度? 根节点记为第0层,带权路径长度= sum(层数*结点权重) 如何构造哈夫曼树:...
  • 最优二叉树(哈夫曼树):给定n个权值{w1,w2,…,wn},试构造一棵有n个叶子结点的二叉树,每个叶子结点带权为wi。构造出来的二叉树的形态可以有多个,我们把其中带权路径长度WPL最小的二叉树称作最优二叉树或者哈夫曼...
  • 例如,有权值分别为 5、10、15、20、25、40的结点,根据以上算法构造出一个哈夫曼树。 (1)取这六个树中最小的两个树5、10连成一个二叉树,其权值为15;此时森林里的树变为15(5、10)、15、20、25、40。 (2)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,044
精华内容 417
关键字:

构造最优二叉树