• 哈夫曼编码原理


    一、应用背景

    给定一段文字,如果我们统计出字母出现的频率,是可以根据哈夫曼算法给出一套编码,使得用此编码压缩原文可以得到最短的编码总长。然而哈夫曼编码并不是唯一的。例如对字符串"aaaxuaxz",容易得到字母 'a'、'x'、'u'、'z' 的出现频率对应为 4、2、1、1。我们可以设计编码 {'a'=0, 'x'=10, 'u'=110, 'z'=111},也可以用另一套 {'a'=1, 'x'=01, 'u'=001, 'z'=000},还可以用 {'a'=0, 'x'=11, 'u'=100, 'z'=101},三套编码都可以把原文压缩到 14 个字节。但是 {'a'=0, 'x'=01, 'u'=011, 'z'=001} 就不是哈夫曼编码,因为用这套编码压缩得到 00001011001001 后,解码的结果不唯一,"aaaxuaxz" 和 "aazuaxax" 都可以对应解码的结果。本题就请你判断任一套编码是否哈夫曼编码。

    输入格式:

    首先第一行给出一个正整数 N(2≤N≤63),随后第二行给出 N 个不重复的字符及其出现频率,格式如下:

    c[1] f[1] c[2] f[2] ... c[N] f[N]
    

    其中c[i]是集合{'0' - '9', 'a' - 'z', 'A' - 'Z', '_'}中的字符;f[i]c[i]的出现频率,为不超过 1000 的整数。再下一行给出一个正整数 M(≤1000),随后是 M 套待检的编码。每套编码占 N 行,格式为:

    c[i] code[i]
    

    其中c[i]是第i个字符;code[i]是不超过63个'0'和'1'的非空字符串。

    输出格式:

    对每套待检编码,如果是正确的哈夫曼编码,就在一行中输出"Yes",否则输出"No"。

    注意:最优编码并不一定通过哈夫曼算法得到。任何能压缩到最优长度的前缀编码都应被判为正确。

    输入样例:

    1. 7
    2. A 1 B 1 C 1 D 3 E 3 F 6 G 6
    3. 4
    4. A 00000
    5. B 00001
    6. C 0001
    7. D 001
    8. E 01
    9. F 10
    10. G 11
    11. A 01010
    12. B 01011
    13. C 0100
    14. D 011
    15. E 10
    16. F 11
    17. G 00
    18. A 000
    19. B 001
    20. C 010
    21. D 011
    22. E 100
    23. F 101
    24. G 110
    25. A 00000
    26. B 00001
    27. C 0001
    28. D 001
    29. E 00
    30. F 10
    31. G 11

    输出样例:

    1. Yes
    2. Yes
    3. No
    4. No

    二、具体实现 

    在完成这道题之前, 首先要对构建哈夫曼树和哈夫曼编码的框架有一定的理解。

    之后再根据题目的要求进行恰当的修改。

    以下代码参考   清华大学 《数据结构》(C语言版)严蔚敏 吴伟民 编著  

    ---------------------------------------数组从1开始,0未用。

    框架代码如下: 

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. #define MAXSIZE 100
    6. typedef struct{
    7. int weight;
    8. int parent,lchild,rchild;
    9. }HTNode,*HuffmanTree;
    10. typedef char** HuffmanCode;
    11. void Init_HuffmanTree(HuffmanTree &HT,int n)
    12. {
    13. int m = 2*n-1;
    14. HT = (HuffmanTree)malloc(sizeof(HTNode)*(m+1));
    15. for(int i=0;i<=m;i++)
    16. {
    17. HT[i].parent=0;
    18. HT[i].lchild=0;
    19. HT[i].rchild=0;
    20. }
    21. }
    22. void Select(HuffmanTree HT,int *S1,int *S2,int m)
    23. {
    24. int minweight=10000;
    25. for(int i=1;i
    26. {
    27. if(HT[i].weight0)
    28. {
    29. minweight = HT[i].weight;
    30. *S1 = i;
    31. }
    32. }
    33. minweight=10000;
    34. for(int i=1;i
    35. {
    36. if(HT[i].weight0 && i!=*S1)
    37. {
    38. minweight = HT[i].weight;
    39. *S2 = i;
    40. }
    41. }
    42. }
    43. void Creat_HuffmanTree(HuffmanTree &HT,int n)
    44. {
    45. int m = 2*n-1;
    46. int s1,s2;
    47. if(n<=1)
    48. return;
    49. for(int i=n+1;i<=m;i++)
    50. {
    51. Select(HT,&s1,&s2,i);
    52. HT[s1].parent=i;
    53. HT[s2].parent=i;
    54. HT[i].lchild=s1;
    55. HT[i].rchild=s2;
    56. HT[i].weight=HT[s1].weight+HT[s2].weight;
    57. }
    58. }
    59. void Print_HuffmanTree(HuffmanTree HT,int n)
    60. {
    61. cout<<"index"<<"\t"<<"weight"<<"\t"<<"parent"<<"\t"<<"lchild"<<"\t"<<"rchild"<
    62. for(int i=1;i<=2*n-1;i++)
    63. {
    64. cout<"\t"<"\t"<"\t"<"\t"<
    65. }
    66. }
    67. void Creat_HuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
    68. {
    69. char *cd;
    70. int c,f,start;
    71. HC = (HuffmanCode)malloc(sizeof(char*)*(n+1));
    72. cd = (char*)malloc(sizeof(char)*n);
    73. cd[n-1]='\0';
    74. for(int i=1;i<=n;i++)
    75. {
    76. start = n-1;
    77. c=i;
    78. f=HT[i].parent;
    79. while(f!=0)
    80. {
    81. start--;
    82. if(HT[f].lchild==c)
    83. cd[start]='0';
    84. else cd[start]='1';
    85. c=f;
    86. f=HT[f].parent;
    87. }
    88. HC[i] = (char*)malloc(sizeof(char)*(n-start));
    89. strcpy(HC[i],&cd[start]);
    90. }
    91. free(cd);
    92. }
    93. void Print_HuffmanCode(char ch[][MAXSIZE],HuffmanCode HC,int n)
    94. {
    95. for(int i=1;i<=n;i++)
    96. {
    97. cout<" ";
    98. cout<
    99. }
    100. }
    101. int main()
    102. {
    103. int n;
    104. char ch[MAXSIZE][MAXSIZE];
    105. HuffmanTree HT;
    106. HuffmanCode HC;
    107. cin>>n;
    108. Init_HuffmanTree(HT,n);
    109. for(int i=1;i<=n;i++)
    110. {
    111. cin>>ch[i]>>HT[i].weight;
    112. }
    113. Creat_HuffmanTree(HT,n);
    114. Creat_HuffmanCode(HT,HC,n);
    115. cout<
    116. Print_HuffmanTree(HT,n);
    117. cout<
    118. Print_HuffmanCode(ch,HC,n);
    119. cout<
    120. return 0;
    121. }

    整体思路总结如下:

     构造最优二叉树(哈夫曼树),a、b、c、d、e的权值依次为7、5、5、2、4 。 程序运行结果如上图2。


     在有了相应的基础后,我们再来看这道题目。

    仔细阅读题目过后我们发现,本题的初衷并不是让我们求出哈夫曼编码。题干说:“注意:最优编码并不一定通过哈夫曼算法得到。任何能压缩到最优长度的前缀编码都应被判为正确。

    这道题的解题思路为:先求出哈夫曼编码的的最短加权路径和然后再判断是不是前缀编码。两者缺一不可。

     完整代码如下:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. #define MAXSIZE 100
    6. typedef struct{
    7. int weight;
    8. int parent,lchild,rchild;
    9. }HTNode,*HuffmanTree;
    10. typedef char** HuffmanCode;
    11. void Init_HuffmanTree(HuffmanTree &HT,int n)
    12. {
    13. int m = 2*n-1;
    14. HT = (HuffmanTree)malloc(sizeof(HTNode)*(m+1));
    15. for(int i=0;i<=m;i++)
    16. {
    17. HT[i].parent=0;
    18. HT[i].lchild=0;
    19. HT[i].rchild=0;
    20. }
    21. }
    22. void Select(HuffmanTree HT,int *S1,int *S2,int m)
    23. {
    24. int minweight=10000;
    25. for(int i=1;i
    26. {
    27. if(HT[i].weight0)
    28. {
    29. minweight = HT[i].weight;
    30. *S1 = i;
    31. }
    32. }
    33. minweight=10000;
    34. for(int i=1;i
    35. {
    36. if(HT[i].weight0 && i!=*S1)
    37. {
    38. minweight = HT[i].weight;
    39. *S2 = i;
    40. }
    41. }
    42. }
    43. void Creat_HuffmanTree(HuffmanTree &HT,int n)
    44. {
    45. int m = 2*n-1;
    46. int s1,s2;
    47. if(n<=1)
    48. return;
    49. //这里需要注意i是从n+1开始的
    50. for(int i=n+1;i<=m;i++)
    51. {
    52. Select(HT,&s1,&s2,i);
    53. HT[s1].parent=i;
    54. HT[s2].parent=i;
    55. HT[i].lchild=s1;
    56. HT[i].rchild=s2;
    57. HT[i].weight=HT[s1].weight+HT[s2].weight;
    58. }
    59. }
    60. void Print_HuffmanTree(HuffmanTree HT,int n)
    61. {
    62. cout<<"index"<<"\t"<<"weight"<<"\t"<<"parent"<<"\t"<<"lchild"<<"\t"<<"rchild"<
    63. for(int i=1;i<=2*n-1;i++)
    64. {
    65. cout<"\t"<"\t"<"\t"<"\t"<
    66. }
    67. }
    68. int Creat_HuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
    69. {
    70. char *cd;
    71. int c,f,start;
    72. HC = (HuffmanCode)malloc(sizeof(char*)*(n+1));
    73. cd = (char*)malloc(sizeof(char)*n);
    74. cd[n-1]='\0';
    75. for(int i=1;i<=n;i++)
    76. {
    77. start = n-1;
    78. c=i;
    79. f=HT[i].parent;
    80. while(f!=0)
    81. {
    82. start--;
    83. if(HT[f].lchild==c)
    84. cd[start]='0';
    85. else cd[start]='1';
    86. c=f;
    87. f=HT[f].parent;
    88. }
    89. HC[i] = (char*)malloc(sizeof(char)*(n-start));
    90. strcpy(HC[i],&cd[start]);
    91. }
    92. int sum=0;
    93. for(int i=n+1;i<2*n;i++)
    94. {
    95. sum+=HT[i].weight;
    96. }
    97. free(cd);
    98. return sum;
    99. }
    100. void Print_HuffmanCode(char ch[][MAXSIZE],HuffmanCode HC,int n)
    101. {
    102. for(int i=1;i<=n;i++)
    103. {
    104. cout<" ";
    105. cout<
    106. }
    107. }
    108. int IsPrefix(char code[MAXSIZE][MAXSIZE],int n) //判断是否是前缀式编码
    109. {
    110. int flag=1; //1为是前缀式编码,0为否
    111. for (int i = 1;i <= n;i++)
    112. {
    113. int j = i+1;
    114. while (j <= n && j!=i && flag == 1)
    115. {
    116. if (strstr(code[j-1], code[i-1]) != code[j-1]) //运用strstr函数
    117. {
    118. if (j == n)
    119. j = 1;
    120. else j++;
    121. }
    122. else {
    123. flag = 0;
    124. }
    125. }
    126. if (flag == 0)
    127. {
    128. break;
    129. }
    130. }
    131. return flag;
    132. }
    133. int main()
    134. {
    135. int n,sum_weight;
    136. char ch[MAXSIZE][MAXSIZE];
    137. HuffmanTree HT;
    138. HuffmanCode HC;
    139. cin>>n;
    140. Init_HuffmanTree(HT,n);
    141. for(int i=1;i<=n;i++)
    142. {
    143. cin>>ch[i]>>HT[i].weight;
    144. }
    145. Creat_HuffmanTree(HT,n);
    146. sum_weight = Creat_HuffmanCode(HT,HC,n);
    147. //Print_HuffmanTree(HT,n);
    148. //Print_HuffmanCode(ch,HC,n);
    149. //开始读入各段哈夫曼编码
    150. int M,sum=0;
    151. cin>>M;
    152. char code[MAXSIZE][MAXSIZE];
    153. char c;
    154. for(int i=1;i<=M;i++)
    155. {
    156. sum=0;
    157. for(int j=0;j
    158. {
    159. cin>>c>>code[j];
    160. sum+=strlen(code[j])*HT[j+1].weight; //求输入的加权路径:编码长度*权重
    161. }
    162. if(sum!=sum_weight) //先判断最短加权路径和是否相等,若不等直接No,若相等,判断是否是前缀码
    163. {
    164. cout<<"No"<
    165. }else{
    166. if(IsPrefix(code,n)==1)
    167. cout<<"Yes"<
    168. else cout<<"No"<
    169. }
    170. }
    171. return 0;
    172. }

    下面我们来看这个代码,注意与第一个框架代码做对比

    • 首先在 Creat_HuffmanCode() 函数中,我们加入如下代码1,来求最短加权路径和。用于与主函数中(代码2)输入的哈夫曼编码作比较。
    • 依照上面的图1,找到相应的数学关系。
    1. //代码1
    2. int sum=0;
    3. for(int i=n+1;i<2*n;i++) //注意从n+1开始
    4. {
    5. sum+=HT[i].weight;
    6. }
    7. return sum;
    1. //代码2
    2. sum=0;
    3. for(int j=0;j
    4. {
    5. cin>>c>>code[j];
    6. sum+=strlen(code[j])*HT[j+1].weight; //求输入的加权路径:编码长度*权重
    7. }

    • 写了 IsPrefix() 函数用于判断是否是前缀码。相应代码的思路如下:

    前缀编码定义:
    (字符集中)任一编码都不是其它字符的编码的前缀


    例:
    (1)找出下面不是前缀编码的选项
    A{1,01,000,001}
    B{1,01,011,010}
    C{0,10,110,11}
    D{0,1,00,11}

    第一步:看A中的第一个数1,看看其他数有没有1开头的。没有。
    第二步:看A中的第二个数01,看看其他数有没有01开头的。没有。
    第三步:看看A中的第三个数000,看看其他数有没有000开头的。没有。
    第四步:看看A中的第四个数001,看看其他数有没有001开头的。没有。
    所以A是前缀编码。

    其他选项也一样。B、C也一样。来说说D:

    第一步,看D中的第一个数,找有0开头的的数,有,是00;其实到这里已经不用看了
    因为D已经不是前缀编码了。
    但第二个数1,是第四个数11的前缀,所以也能作为D不是前缀编码的理由。

      IsPrefix() 函数中应用了strstr()函数。解释如下:


    最后,题干的测试数据

    1. 4
    2. a 4
    3. x 2
    4. u 1
    5. z 1
    6. 4
    7. a 0
    8. x 10
    9. u 110
    10. z 111
    11. a 1
    12. x 01
    13. u 001
    14. z 000
    15. a 0
    16. x 11
    17. u 100
    18. z 101
    19. a 0
    20. x 01
    21. u 011
    22. z 001

  • 相关阅读:
    VBA和VBS之不同
    AirServer2024免费功能强大的投屏软件
    Redis订阅模式在生产环境引起的内存泄漏
    Web版RSS阅读器yarr
    21.Hadoop在Windows环境下的下载安装配置超详细版
    818专业课【考经】—《信号系统》之章节概要:第五章 连续时间信号的变换域分析
    Day16:C++之STL应用篇(推箱子cxk限定)
    每日一个设计模式之【工厂模式】
    Oracle-数据库对象的使用
    【Vue 开发实战】基础篇 # 6:双向绑定和单向数据流不冲突
  • 原文地址:https://blog.csdn.net/a15608445683/article/details/125882298