哈夫曼树

≯℡__Kan透↙ 提交于 2019-12-19 12:39:28
哈夫曼树(Huffman)树又称最优二叉树,是指对于一组带有确定权值的叶子结点所构造的具有带权路径长度最短的二叉树。从树中一个结点到另一个结点之间的分支构成了两结点之间的路径,路径上的分支个数称为路径长度。二叉树的路径长度是指由根结点到所有叶子结点的路径长度之和。如果二叉树中的叶子结点都有一定的权值,则可将这一概念拓展:设二叉树具有n个带权值的叶子结点,则从根结点到每一个叶子结点的路径长度与该叶子结点权值的乘积之和称为二叉树路径长度,记做:
WPL=W1L1+W2L2+......+WnLn;
其中:n为二叉树中叶子结点的个数;Wk为第k个叶子的权值;Lk为第k个叶子结点的路径长度。
 
若给定n个权值,如何构造一棵具有n个给定权值叶子结点的二叉树,使得其带权路径长度WPL最小?哈夫曼根据"权值大的结点尽量靠近根"这一原则,给出了一个带有一般规律的算法,称为"哈夫曼"算法,哈夫曼算法如下: 
(1)根据给定n个权值{w1,w2,....,wn}构成n棵二叉树的集合F={T1,T2,.....,Tn};其中,每棵二叉树Ti(1<=i<=n)只有一个带权值wi的根结点,其左、右子树均为空。
(2)在F中选取两棵根结点权值最小的二叉树作为左、右子树来构造一棵新的二叉树,且置新的二叉树根结点权值为其左右子树根结点的权值之和。
(3)在F中删除这两棵树,同时将生成新的二叉树加入到F中。
(4)重复(2)(3),直到F中只剩下一棵二叉树加入到F中。
 
从哈夫曼算法可以看出,初始化共有n棵二叉树,且均只有一个根结点。在哈夫曼树的构造过程中,每次都是选取两棵根结点权值最小的二叉树合并成一棵新的二叉树,为此需要增加一个结点作为新的根结点,而这两棵权值最小的二叉树最终合并成一棵新的二叉树,为此需要增加一个结点作为新二叉树的根结点,而这两棵权值最小的二叉树则作为根结点的左、右子树。由于要进行n-1次合并才能使初始化的n棵二叉树最终合并为一棵二叉树,因此n-1次合并共产生了n-1个新结点,即最终生成的哈夫曼树共有2n-1个结点。由于每次都是将两棵权值最小的二叉树合并生成一棵新二叉树,所以生成的哈夫曼树中没有度为1的结点:并且,两棵权值最小的二叉树合并生成一棵新的二叉树,所以生成的哈夫曼树中没有度为1的结点;并且,两棵权值最小的二叉树那棵作为作为左子树、那棵作为右子树,哈夫曼算法并没有要求,故最终构造出来的哈夫曼树并不唯一,但是最小的WPL值是唯一的。所以,哈夫曼具有如下几个特点:
(1)对给定的权值,所构造的二叉树具有的最小WPL;
(2)权值大的结点离根近,权值小的结点离根远;
(3)所生成的二叉树不唯一
(4)没有度为1的结点
 
        哈夫曼树与哈夫曼编码1
概述
 为了构造哈夫曼树,首先修改二叉树的存储结构。哈夫曼树除了二叉树原有的数据域data和左、右孩子指针域*lchild、*rchild外,还增加了一个指针域*next,即哈夫曼树的结点同时又是单链表的结点。在输入哈夫曼树叶子结点权值时,我们将这些权值结点链成一个升序链表。在构造哈夫曼树时,每次取升序单链表的前两个数据结点来构造一个哈夫曼树的树枝结点,同时删去单链表中的这两个数据结点,并将该树枝结点按升序再插入到单链表中。这种构造哈夫曼树树枝结点的过程一直持续到单链表为空为止,且最后生成的树枝结点即为哈夫曼树的树根结点。
 对所生成的哈夫曼树,我们用二叉树后序非递归方法遍历这棵哈夫曼树,遍历到叶结点时输出该叶结点的值及对应的哈夫曼编码。
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #define MAXSIZE 30
  4 
  5 typedef struct node
  6 {
  7     int data;//结点数据
  8     struct node *lchild,*rchild;//哈夫曼树的左右孩子指针
  9     struct node *next;//哈夫曼树的结点同时又是单链表的结点,next为单链表的结点指针
 10 }BSTree_Link;//二叉树及单链表结点类型
 11 
 12 BSTree_Link *CreateLinkList(int n)//根据叶子结点的权值生成一个升序单链表
 13 {
 14     BSTree_Link *link,*p,*q,*s;
 15     int i;
 16     link=(BSTree_Link*)malloc(sizeof(BSTree_Link));//生成单链表的头结点
 17     s=(BSTree_Link*)malloc(sizeof(BSTree_Link));//生成单链表的第一个数据结点,同时也是哈夫曼树的叶结点
 18     scanf("%d",&s->data);//输入叶子结点的权值
 19     s->lchild=NULL;
 20     s->rchild=NULL;//置左、右孩子指针为空的叶结点标志
 21     s->next=NULL;//置单链表链尾结点标志
 22     link->next=s;
 23     for(i=2;i<=n;i++)//生成单链表剩余的n-1个数据结点
 24     {
 25         s=(BSTree_Link*)malloc(sizeof(BSTree_Link));//生成一个数据结点
 26         scanf("%d",&s->data);//输入叶子结点的权值
 27         s->lchild=NULL;
 28         s->rchild=NULL;//置左右孩子指针为空的叶结点标志
 29         q=link;//将该数据结点按升序插入到单链表中
 30         p=q->next;
 31         while(p!=NULL)
 32             if(s->data>p->data)//查找插入位置
 33             {
 34                 q=p;
 35                 p=p->next;
 36             }
 37             else//找到插入位置后进行插入
 38             {
 39                 q->next=s;
 40                 s->next=p;
 41                 break;
 42             }
 43             if(s->data>q->data)//插入到链尾的处理
 44             {
 45                 q->next=s;
 46                 s->next=p;
 47             }
 48     }
 49     return link;//返回升序单链表的头指针结点
 50 }
 51 
 52 void print(BSTree_Link *h)//输出单链表
 53 {
 54     BSTree_Link *p;
 55     p=h->next;
 56     while(p!=NULL)
 57     {
 58         printf("%d,",p->data);
 59         p=p->next;
 60     }
 61     printf("\n");
 62 }
 63 
 64 BSTree_Link *HuffTree(BSTree_Link *link)//生成哈夫曼树
 65 {
 66     BSTree_Link *p,*q,*s;
 67     while(link->next!=NULL)//当单链表的数据结点非空时
 68     {
 69         p=link->next;//取出升序链表中的第一个数据结点
 70         q=p->next;//取出升序链表中的第二个数据结点
 71         link->next=q->next;//使头结点的指针指向单链表的第三个数据结点
 72         s=(BSTree_Link*)malloc(sizeof(BSTree_Link));//生成哈夫曼树的树枝结点
 73         s->data=p->data+q->data;//该树枝结点的权值为取出的二个数据结点权值之和
 74         s->lchild=p;//取出的第一个数据结点作为该树枝结点的左孩子
 75         s->rchild=q;//取出的第二个数据结点作为该树枝结点的右孩子
 76         q=link;//将该树枝结点按升序插入到单链表中
 77         p=q->next;
 78         while(p!=NULL)
 79             if(s->data>p->data)
 80             {
 81                 q=p;
 82                 p=p->next;
 83             }
 84             else
 85             {
 86                 q->next=s;
 87                 s->next=p;
 88                 break;
 89             }
 90             if(q!=link&&s->data>q->data)//插入到链尾的处理,如果q等于link则链表为空,此时*s即为根结点
 91             {
 92                 q->next=s;
 93                 s->next=p;
 94             }
 95     }
 96     return s;//当单链表为空时,最后生成的树枝结点即为哈夫曼树的根节点
 97 }
 98 
 99 void Preorder(BSTree_Link *p)//先序遍历二叉树
100 {
101     if(p!=NULL)
102     {
103         printf("%4d",p->data);//访问根结点
104         Preorder(p->lchild);//访问左子树
105         Preorder(p->rchild);//访问右子树
106     }
107 }
108 
109 void Inorder(BSTree_Link *p)//中序遍历二叉树
110 {
111     if(p!=NULL)
112     {
113         Preorder(p->lchild);//访问左子树
114         printf("%4d",p->data);//访问根结点
115         Preorder(p->rchild);//访问右子树
116     }
117 }
118 void HuffCode(BSTree_Link *p)//后序遍历哈夫曼树并输出哈夫曼树编码
119 {
120     BSTree_Link *stack[MAXSIZE],*q;
121     int b,i=-1,j=0,k,code[MAXSIZE];
122     do//后序遍历二叉树
123     {
124         while(p!=NULL)//将*p结点左分支上的左孩子入栈
125         {
126             if(p->lchild==NULL&&p->rchild==NULL)
127             {
128                 printf("key=%3d,code:",p->data);//输出叶结点信息
129                 for(k=0;k<j;k++)//输出该叶结点的哈夫曼编码
130                     printf("%d",code[k]);
131                 printf("\n");
132                 j--;
133             }
134             stack[++i]=p;//指向当前结点的指针p入栈
135             p=p->lchild;//p指向*p的左孩子
136             code[j++]=0;//对应的左分支置编码0
137         }
138         //栈顶结点已没有左孩子或其左子树上的结点都已访问过
139         q=NULL;
140         b=1;//置已访问过的标记
141         while(i>=0&&b)//栈stack不空且当前栈顶结点的左子树已经遍历过
142         {
143             p=stack[i];//取出当前栈顶存储的结点指针
144             if(p->rchild==q)//当前栈顶结点*p无右孩子或*p的右孩子已访问过
145             {
146             i--;
147             j--;
148             q=p;//q指向刚访问过的结点*p
149             }
150     else//当前栈顶结点*p有右子树
151         {
152             p=p->rchild;//p指向当前栈顶结点*p的右孩子结点
153             code[j++]=1;//对应的右分支置编码1
154             b=0;//置右孩子结点未遍历过其右子树标记
155             }
156         }
157     }while(i>=0);//当栈stack非空时继续遍历
158 }
159 
160 void main()
161 {
162     BSTree_Link *root;
163     int n;
164     printf("Input number of keys\n");//输入叶子结点的个数
165     scanf("%d",&n);
166     printf("Input keys:\n");//输入n个叶子结点的权值
167     root=CreateLinkList(n);//根据叶子结点的权值生成一个升序单链表
168     printf("Output list:\n");//输出所生成的升序单链表
169     print(root);
170     root=HuffTree(root);//生成哈夫曼树
171     printf("Inorder output HuffTree:\n");//先序遍历输出哈夫曼树各结点的值
172     Inorder(root);
173     printf("\n");
174     printf("Preorder output HuffTree:\n");//先序遍历输出哈夫曼树各结点的值
175     Preorder(root);
176     printf("\n");
177     printf("Output Code  of HuffTree:\n");//后序遍历哈夫曼树构造并输出哈夫曼编码
178     HuffCode(root);
179 }

说明:例如,对8个权值分别为7,19,2,6,32,3,21,10的叶子结点,生成的哈夫曼树由树根到树叶路径上标识的哈夫曼树示意图:


打印结果:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!