(一)哈夫曼树的概念和算法实现
哈夫曼树(最优二叉树):对于给定的一系列带权的叶子结点,带权路径长度(WPL)最短的二叉树
构造一棵哈夫曼树的算法:由n棵树组成的森林一步步合成一棵最优二叉树
1.初始n个叶子结点互相独立,形成n棵树组成的森林
2.做n-1次合并操作:
每次在现有根节点中寻找权值最小的两个结点合并成一棵二叉树,新树的根结点的权值即为原来两个权值之和
在森林中插入新树,删去已合并的两棵老树
3.直到森林中只剩下一棵树,则该树即为所求
由上构造过程,采用数组存储不会带来空间上的浪费而且便于检索。
1 struct node{ 2 int weight; 3 int parent; 4 int LChild,RChild; 5 }HTree[MAX_M+10];
构造代码1:每次暴力检索两个最小值
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 void select(int right,int *c1,int *c2){ 2 int MIN1=INF,MIN2=INF;//最小和次小权值 3 int MIN1_num=0,MIN2_num=0;//最小和次小结点编号 4 for (int i=1;i<=right;i++){ 5 if (HTree[i].parent!=-1) continue; 6 if (HTree[i].weight<MIN1){ 7 //printf("%d\n",i); 8 MIN2=MIN1; 9 MIN2_num=MIN1_num; 10 MIN1=HTree[i].weight; 11 MIN1_num=i; 12 } 13 else if (HTree[i].weight<MIN2){ 14 MIN2=HTree[i].weight; 15 MIN2_num=i; 16 } 17 } 18 *c1=MIN1_num; 19 *c2=MIN2_num; 20 } 21 void Create_HTree(int n,int w[]){ 22 //初始化 23 int m=2*n-1;//结点总数 24 for (int i=1;i<=m;i++){ 25 if (i<=n) HTree[i].weight=w[i]; 26 HTree[i].parent=-1; 27 HTree[i].LChild=0; 28 HTree[i].RChild=0; 29 } 30 for (int i=n+1;i<=m;i++){ 31 int c1,c2;//选中的两个最值结点编号 32 select(i-1,&c1,&c2); 33 HTree[i].weight=HTree[c1].weight+HTree[c2].weight; 34 HTree[i].LChild=c1;HTree[i].RChild=c2; 35 HTree[c1].parent=i;HTree[c2].parent=i; 36 } 37 }
构造代码2:用最小堆辅助
![](/qrcode.jpg)
//留坑
(二)相关代码
1.求树的WPL值:从叶子结点向上逐层计数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 int Calc_wpl(int n){ 2 int ans=0; 3 for (int i=1;i<=n;i++){ 4 int last_p=HTree[i].parent; 5 int temp=0; 6 while (last_p!=-1){ 7 temp++; 8 last_p=HTree[last_p].parent; 9 } 10 ans+=temp*HTree[i].weight; 11 } 12 return ans; 13 }
//
(三)哈夫曼树应用之一:哈夫曼编码
1.对字符串进行编码时需要关注的问题:
(1)编码长度
ASCII码--等长码;
若希望编码总长度尽可能小,一种自然的想法是使出现频率高的字符的编码尽量短,频率低的字符编码适当长一些
(2)避免二义性
前缀码:生成的所有代码串均不是其他串的前缀。这样子的编码系统就具有唯一的对应明文,不需要分隔符
2.哈夫曼编码:对一棵哈夫曼树进行左0右1(或相反)的编码方式,每个叶子均得到从根到它的路径上的01编码,这样得到的即为哈弗曼编码
哈夫曼树的特性保证了哈夫曼编码是最优前缀码。
3.相关题目
ZJU MOOC 05-树9 Huffman Codes
总体思路:一是判断编码长度是否等于哈弗曼编码的长度,二是判断是否前缀码
solution1:偷懒不储存字符不生成哈夫曼码,只用频率当做权值求出wpl(即为哈夫曼码长度),然后O(n^2)暴力判断是否前缀码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<string.h> 4 #include<cmath> 5 #include<algorithm> 6 using namespace std; 7 #define MAX_N 100 8 #define MAX_M 2*MAX_N-1 9 #define INF 70001000 10 struct NODE{ 11 int weight; 12 int parent; 13 int LChild,RChild; 14 }HTree[MAX_M+10]; 15 void select(int right,int *c1,int *c2); 16 void Create_HTree(int n,int w[]); 17 int Calc_wpl(int n); 18 bool ok_cmp(char s1[],char s2[]){ 19 for (int i=0;i<min(strlen(s1),strlen(s2));i++) 20 if (s1[i]!=s2[i]) return 0; 21 return 1; 22 } 23 bool is_correct(int STD,int n,int num[],char s[MAX_N][MAX_N]){ 24 int cnt=0; 25 for (int i=1;i<=n;i++){ 26 cnt+=strlen(s[i])*num[i]; 27 if (cnt>STD) return 0; 28 } 29 if (cnt!=STD) return 0; 30 //判断是否前缀码 31 for (int i=1;i<n;i++) 32 for (int j=i+1;j<=n;j++) 33 if (ok_cmp(s[i],s[j])) return 0; 34 return 1; 35 } 36 int main(){ 37 int n; 38 int num[MAX_N]; 39 scanf("%d",&n); 40 for (int i=1;i<=n;i++){ 41 scanf("%*c%*c%d%*c",&num[i]); 42 } 43 Create_HTree(n,num); 44 int standard=Calc(n); 45 int t; 46 scanf("%d\n",&t); 47 while (t--){ 48 char sub[MAX_N][MAX_N]={""}; 49 for (int i=1;i<=n;i++) 50 scanf("%*c%*c%s",sub[i]); 51 printf("%s\n",is_correct(standard,n,num,sub)?"Yes":"No"); 52 } 53 return 0; 54 }
solution2:求出haffman code,建树判前缀码 //留坑