<数据结构与算法>最优二叉树——霍夫曼树

来源:互联网 发布:郑州景安网络怎么样 编辑:程序博客网 时间:2024/06/07 09:08

一:什么是最优二叉树?

最优二叉树就是从已给出的目标带权结点(单独的结点) 经过一种方式的组合形成一棵树.使树的权值最小. 最优二叉树是带权路径长度最短的二叉树。根据结点的个数,权值的不同,最优二叉树的形状也各不相同。它们的共同点是:带权值的结点都是叶子结点。权值越小的结点,其到根结点的路径越长。

官方定义:

在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为最优二叉树哈夫曼树

二:下面先弄清几个几个概念:

1.路径长度

在树中从一个结点到另一个结点所经历的分支构成了这两个结点间的路径上的分支数称为它的路径长度。

2.树的路径长度
     树的路径长度是从树根到树中每一结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。
3.树的带权路径长度(Weighted Path Length of Tree,简记为WPL)
  结点的权
:在一些应用中,赋予树中结点的一个有某种意义的实数。
  结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。
  树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为: 
                
  其中:
 
   n表示叶子结点的数目
    wi
li分别表示叶结点ki的权值和根到结点ki之间的路径长度。
    
树的带权路径长度亦称为树的代价。

三:用一个例子来理解一下以上概念

【例】给定4个叶子结点a,b,c和d,分别带权7,5,2和4。构造如下图所示的三棵二叉树(还有许多棵),它们的带权路径长度分别为:


        (a)WPL=7*2+5*2+2*2+4*2=36
        (b)WPL=7*3+5*3+2*1+4*2=46
        (c)WPL=7*1+5*2+2*3+4*3=35

其中(c)树的WPL最小,可以验证,它就是哈夫曼树。


注意:
    ① 叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。
    ② 最优二叉树中,权越大的叶子离根越近。
    ③ 最优二叉树的形态不唯一,WPL最小。

 

四.哈夫曼算法

对于给定的叶子数目及其权值构造最优二叉树的方法,由于这个算法是哈夫曼提出来的,故称其为哈夫曼算法。其基本思想是:
  (1)根据给定的n个权值wl,w2,…,wn构成n棵二叉树的森林F={T1,T2,…,Tn},其中每棵二叉树Ti中都只有一个权值为wi的根结点,其左右子树均空。
  (2)在森林F中选出两棵根结点权值最小的树(当这样的树不止两棵树时,可以从中任选两棵),将这两棵树合并成一棵新树,为了保证新树仍是二叉树,需 要增加一个新结点作为新树的根,并将所选的两棵树的根分别作为新根的左右孩子(谁左,谁右无关紧要),将这两个孩子的权值之和作为新树根的权值。
  (3)对新的森林F重复(2),直到森林F中只剩下一棵树为止。这棵树便是哈夫曼树。 
  
注意:
    ① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子
    ② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。
    ③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。

五:最优二叉树算法具体实现思路

在构造哈夫曼树时,可以设置一个结构数组HuffNode保存哈夫曼树中各结点的信息,根据二叉树的性质可知,具有n个叶子结点的哈夫曼树共有2n-1个结点,所以数组HuffNode的大小设置为2n-1,数组元素的结构形式如下:  

weight

lchild

rchild

parent

 


其中,weight域保存结点的权值,lchild和rchild域分别保存该结点的左、右孩子结点在数组HuffNode中的序号,从而建立起结 点之间的关系。为了判定一个结点是否已加入到要建立的哈夫曼树中,可通过parent域的值来确定。初始时parent的值为-1,当结点加入到树中时, 该结点parent的值为其双亲结点在数组HuffNode中的序号,就不会是-1了。构造哈夫曼树时,首先将由n个字符形成的n个叶结点存放到数组HuffNode的前n个分量中,然后根据前面介绍的哈夫曼方法的基本思想,不断将两个小子树合并为一个较大的子树,每次构成的新子树的根结点顺序放到HuffNode数组中的前n个分量的后面。

/*霍夫曼编码的C语言实现二*/#include <stdio.h>#include <stdlib.h>#include <string.h> #define MAXBIT 100#define MAXVALUE 10000#define MAXLEAF 30#define MAXNODE MAXLEAF*2 -1 //编码结构体typedef struct {    int bit[MAXBIT]; //MAXBIT = 100  最多存放100个编码    int start;}HCodeType;        //结点结构体typedef struct{    int weight; //权    int parent;  //父节点    int lchild;  //左孩子节点    int rchild;  //右孩子节点    int value; //实际值}HNodeType;         /* 构造一颗哈夫曼树 */void HuffmanTree(HNodeType HuffNode[MAXNODE], int n)  //MAXNODE = 30*2 -1 = 59{     /* i、j: 循环变量,m1、m2:构造哈夫曼树不同过程中两个最小权值结点的权值,      x1、x2:构造哈夫曼树不同过程中两个最小权值结点在数组中的序号。*/    int i, j, m1, m2, x1, x2;    /* 初始化存放哈夫曼树数组 HuffNode[] 中的结点 */    for (i=0; i<2*n-1; i++)    {        HuffNode[i].weight = 0;//权值         HuffNode[i].parent =-1;        HuffNode[i].lchild =-1;        HuffNode[i].rchild =-1;        HuffNode[i].value=i; //实际值,可根据情况替换为字母      }     //输入 n 个叶子结点的权值     for (i=0; i<n; i++)    {        printf("输入第%d个节点的权值:\n", i);        scanf("%d", &HuffNode[i].weight);    }     /* 循环构造 Huffman 树 */    for (i=0; i<n-1; i++)    {        m1 = m2 = MAXVALUE; /* MAXVALUE = 10000 //m1、m2中存放两个无父结点且结点权值最小的两个结点 */        x1 = x2 = 0;        /* 找出所有结点中权值最小、无父结点的两个结点,并合并之为一颗二叉树 */        for (j=0; j<n+i; j++)        {            if (HuffNode[j].weight < m1 && HuffNode[j].parent == -1)            {                m2 = m1;                 x2 = x1;                 m1 = HuffNode[j].weight;                x1 = j;            }            else if (HuffNode[j].weight < m2 && HuffNode[j].parent==-1)            {                m2=HuffNode[j].weight;                x2=j;            }        } /* end for */            /* 设置找到的两个子结点 x1、x2 的父结点信息 */        HuffNode[x1].parent  = n+i;        HuffNode[x2].parent  = n+i;        HuffNode[n+i].weight = HuffNode[x1].weight + HuffNode[x2].weight;        HuffNode[n+i].lchild = x1;        HuffNode[n+i].rchild = x2;         printf ("第%d次==》左节点和右节点的权值为: %d, %d\n", i+1, HuffNode[x1].weight, HuffNode[x2].weight);  /* 用于测试 */        printf ("\n");    } /* end for */  /*  for(i=0;i<n+2;i++)    {        printf(" Parents:%d,lchild:%d,rchild:%d,value:%d,weight:%d\n",HuffNode[i].parent,HuffNode[i].lchild,HuffNode[i].rchild,HuffNode[i].value,HuffNode[i].weight);                  }*///测试 } /* end HuffmanTree */ //解码 void decodeing(char string[],HNodeType Buf[],int Num){  int i,tmp=0,code[1024];  int m=2*Num-1;  char *nump;  char num[1024];  for(i=0;i<strlen(string);i++)  {if(string[i]=='0')num[i]=0;        elsenum[i]=1;                      }   i=0;nump=&num[0];  while(nump<(&num[strlen(string)])){tmp=m-1;while((Buf[tmp].lchild!=-1)&&(Buf[tmp].rchild!=-1)){if(*nump==0){tmp=Buf[tmp].lchild ;          } else {tmp=Buf[tmp].rchild;}nump++;     } printf("%d",Buf[tmp].value);                                  }printf("\n");} int main(void){     HNodeType HuffNode[MAXNODE];            /* 定义一个结点结构体数组 */    HCodeType HuffCode[MAXLEAF],  cd;       /* 定义一个编码结构体数组, 同时定义一个临时变量来存放求解编码时的信息 */    int i, j, c, p, n;    char pp[100];    printf ("输入编码数量:");    scanf ("%d", &n);    HuffmanTree (HuffNode, n);         for (i=1; i < n; i++)    {        cd.start = n-1;        c = i;        p = HuffNode[c].parent;        while (p != -1)   /* 父结点存在 */        {            if (HuffNode[p].lchild == c)                cd.bit[cd.start] = 0;            else                cd.bit[cd.start] = 1;            cd.start--;        /* 求编码的低一位 */            c=p;                                p=HuffNode[c].parent;    /* 设置下一循环条件 */        } /* end while */                /* 保存求出的每个叶结点的哈夫曼编码和编码的起始位 */        for (j=cd.start+1; j<n; j++)        { HuffCode[i].bit[j] = cd.bit[j];}        HuffCode[i].start = cd.start;    } /* end for */        /* 输出已保存好的所有存在编码的哈夫曼编码 */    for (i=0; i<n; i++)    {        printf ("%d '的哈夫曼编码是: ", i);        for (j=HuffCode[i].start+1; j < n; j++)        {            printf ("%d", HuffCode[i].bit[j]);        }        printf(" start:%d",HuffCode[i].start);        printf ("\n");      }/*  for(i=0;i<n;i++){for(j=0;j<n;j++){printf ("%d", HuffCode[i].bit[j]);           }        printf("\n");    }*/    printf("解码(输入二进制编码):\n");    scanf("%s", pp);decodeing(pp, HuffNode, n);    getchar();    return 0;}


原创粉丝点击