ps: 怠惰了,,晚上十点才开始写…
数据结构实验六——赫夫曼树及赫夫曼编码算法实现
一、实验目的
熟悉掌握 Huffman 树的构造方法及 Huffman 编码的应用,了解 Huffman 树在通信、编码领域的应用过程。
二、实验内容
(1) 输入一段 100—200 字的英文短文,存入一文件 a 中。
(2) 写函数统计短文出现的字母个数 n 及每个字母的出现次数
(3)写函数以字母出现次数作权值,建 Haffman 树(n 个叶子),给出每个字母的Haffman 编码。
(4)用每个字母编码对原短文进行编码,码文存入文件 b 中。
(5) 用 Haffman 树对文件 b 中码文进行译码,结果存入文件 c 中,比较a,c 是否一致,以检验编码、译码的正确性。
三、问题描述
问题概况:统计文件 a 中一串短文字母个数以及每个字母出现次数,并且以字母出现次数作权值建 Huffman 树,给出每个字母的Huffman 编码,将原文进行编码存到文件 b;用 Huffman 树对 b 中码文进行译码结果存到文件 c 中。
四、数据结构定义
Huffman 树、字母编码采用顺序存储结构,数据元素类型整型和字符型。
五、算法思想及算法设计
题目:
(1) 输入一段 100—200 字的英文短文,存入一文件 a 中。
(2) 写函数统计短文出现的字母个数 n 及每个字母的出现次数
(3) 写函数以字母出现次数作权值,建 Haffman 树(n 个叶子),给出每个字母的Haffman 编码。
(4) 用每个字母编码对原短文进行编码,码文存入文件 b 中。
(5) 用 Haffman 树对文件 b 中码文进行译码,结果存入文件 c 中,比较
a,c 是否一致,以检验编码、译码的正确性。
思考:
- 针对 (1),要考虑英文短文是否为一个自然段(否则需要换行输入),通过一些基础的文件操作将其输入文件 a 中。
- 针对 (2),通过文件操作,得到字母个数和每个字母出现次数,次数存到一个整型数组 w 中,并且默认 w 从下标 0 开始存放 abcdef 等小写字母的次数,从下标 26开始存放 ABCDEF 等大写字母的次数,下标 52 存放空格字符的次数(这里没有考虑英文短文中的逗号句号等其他字符)。
- 针对 (3),这里借鉴了教材中的 Huffman 编码。短文中出现过的字母种类,按字母表上的先后,将其编码一一存放到 HC 二维数组中,并且输出。
- 针对 (4),利用 (3) 中的 HC 数组,对原文编码,通过文件操作输入到 b 中。
- 针对 (5), 通过文件操作获取 b 中编码,结合 Huffman 树,得到应有的字符,并将字符一一输入到文件 c 中。
<1> 输入一个短文到文件 a 中:
Status Puta()
{
int i = 0, j = 0;
char ch[6][1000]; // 这里默认短文不超过 6 行 1000 列,每个字母算一列
for (i = 0; i < 6; i++)
{
fgets(ch[i], sizeof(ch[i]), stdin); // 必须输入六行,不足六行直接输入回车即可
}
FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "w"); // 不同环境下运行此代码需要修改此地址
while (j < i)
{
fputs(ch[j], fp);
j++;
}
fclose(fp);
return OK;
}
<2> 获得权重(字母出现次数):
Status GetWeight(int *w)
{
FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r"); // 不同环境下运行此代码需要修改此地址
if (fp == NULL)
{
printf("OPEN error!");
return ERROR;
}
char ch;
int i, j;
while ((ch = fgetc(fp)) != EOF) // ch 获取文件 a 每个字符
{
i = 0;
j = 0;
if (ch == '\0');
else if (ch == ' ') { w[52]++; continue; }
else if ((int)ch >= 97 && (int)ch <= 122)
{
while (i < 26)
{
if (ch == 'a' + i) { w[i]++; break; } // 例:假如 i=1 执行此句,那么默认字符'b’出现次数 +1
i++;
}
}
else if ((int)ch >= 65 && (int)ch <= 90)
{
i = 26;
while (i < 52)
{
if (ch == 'A' + j) { w[i]++; break; } // 意思同上
i++;
j++;
}
}
}
int n = 0;
for (i = 0; i < 52; i++)
{
if (w[i] != 0) n++; // 这里不含空格字符的字母种类数,这是为了统计初始有几个 Huffman 树根节点
}
fclose(fp);
return n;
}
<3> 输出字母总数和每个字母出现次数:
Status ReadW(int* w)
{
int sum = 0, i = 0, j = 0, cnt = 0;
while (i < 52)
{
sum = sum + w[i];
i++;
}
i = 0;
printf("This passage has %d words\n", sum); // 输出字母总数
while (i < 26)
{
printf("%c: %d ", 'a' + i, w[i]); // 例:假如 i=2 执行此句,则输出 c 的出现次数
cnt++;
i++;
if (cnt == 4) {
cnt = 0;
printf("\n");
}
}
cnt = 0;
printf("\n");
while (i < 52)
{
printf("%c: %d ", 'A' + j, w[i]); // 意思同上
i++;
j++;
cnt++;
if (cnt == 4) {
cnt = 0;
printf("\n");
}
}
return OK;
}
<4>Huffman 树:
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n)
{
HuffmanTree p;
int i, s1, m2, j = 0;
if (n <= 1)
return;
int m = 2 * n - 1; // 有 n 个叶子结点的 Huffman 树有 2*n-1 个节点
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
for (i = 1; i <= n; ++i)
{
while (w[j] == 0)
{
// 由于 w 默认 52 个字母和 1 个空格字符,英文短文不一定全会出现这 53 个字符,
// 所以将权重不为 0 的赋给 Huffman 树根节点
j++;
}
HT[i].weight = w[j];
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
j++;
}
for (; i <= m; ++i)
{
// 超过 n 的部分先赋 0
HT[i].weight = 0;
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
for (i = n + 1; i <= m; ++i)
{
Select(HT, i - 1, s1, m2); // 在 HT 中挑选 parent=0, 而且权重最小的两个节点
HT[s1].parent = i;
HT[m2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = m2;
HT[i].weight = HT[s1].weight + HT[m2].weight;
}
HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));
char* cd = (char*)malloc(n * sizeof(char));
cd[n - 1] = '\0'; // 结束符
for (i = 1; i <= n; ++i)
{
// 从叶子节点到根节点,逆向求编码
int start = n - 1;
for (int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
{
if (HT[f].lchild == c)
cd[--start] = '0'; // 左 0 右 1
else
cd[--start] = '1';
}
HC[i] = (char*)malloc((n - start) * sizeof(char));
strcpy(HC[i], &cd[start]);
}
free(cd);
}
<5>Select 函数:
Status Select(HuffmanTree HT, int total, int &s1, int &m2)
{
int min;
// 得到最小权重s1
for (int i = 1; i <= total; i++)
{
if (HT[i].parent == 0)
{
min = HT[i].weight;
s1 = i;
}
else
{
continue;
}
i = s1 + 1;
while (i <= total)
{
if (min > HT[i].weight && HT[i].parent == 0)
{
min = HT[i].weight;
s1 = i;
}
i++;
}
break;
}
// 得到次小权重m2
for (int i = 1; i <= total; i++)
{
if (HT[i].parent == 0)
{
if (i == s1)
{
continue;
}
min = HT[i].weight;
m2 = i;
}
else
{
continue;
}
i = m2 + 1;
while (i <= total)
{
if (min > HT[i].weight && HT[i].parent == 0)
{
if (i == s1)
{
i++;
continue;
}
min = HT[i].weight;
m2 = i;
}
i++;
}
break;
}
return OK;
}
<6> 输出每个字符的 Huffman 编码、将编码写入 b 中、将译码写入 c 中:
void ReadHuffmanCode(HuffmanTree HT, HuffmanCode HC, int* w, int n) {
char Huffman[53][50];
int i, j = 1, cnt = 0;
for (i = 0; i < 26; ++i) {
if (w[i] != 0) {
printf("%c: %s ", 'a' + i, HC[j]);//由于 HC 存放的是出现过的字母种类编码,
//而对于后续译码操作无法进行,需要 copy 到另一个二维数组,下标含义默认和w数组相同
strcpy(Huffman[i], HC[j]);
j++;
cnt++;
if (cnt == 4)
printf("\n");
}
}
for (i = 26; i < 52; ++i) {
if (w[i] != 0) {
printf("%c: %s ", 'A' + i - 26, HC[j]);
strcpy(Huffman[i], HC[j]);//后续是将编码写入 b 文件的操作
j++;
cnt++;
if (cnt == 4)
printf("\n");
}
}
strcpy(Huffman[52], HC[j]);
FILE* fp2 = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
FILE* fp1 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "w");
char ch;
while ((ch = fgetc(fp2)) != EOF) {
i = 0; j = 0;
if (ch == ' ') {
fputs(Huffman[52], fp1);
continue;
} else if ((int)ch >= 97 && (int)ch <= 122) {
while (i < 26) {
if (ch == 'a' + i) {
fputs(Huffman[i], fp1);
break;
}
i++;
}
} else if ((int)ch >= 65 && (int)ch <= 90) {
i = 26;
while (i < 52) {
if (ch == 'A' + j) {
fputs(Huffman[i], fp1);
break;
}
i++;
j++;
}
}
}
fclose(fp1);
fclose(fp2);
//后续是将译码写入 c 文件的操作
FILE* fp3 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "r");
FILE* fp4 = fopen("C:\\Users\\LENOVO\\Desktop\\c.txt", "w");
int m = 2 * n - 1;
char dh, eh[53];//eh 数组用来记录一个字符的哈夫曼编码
int t = m, k = 0;
j = 0;
while ((dh = fgetc(fp3)) != EOF) {
label:
if (dh == '0') {
t = HT[t].lchild;
if (t != 0) {
eh[k] = dh;
k++;
}
} else if (dh == '1') {
t = HT[t].rchild;
if (t != 0) {
eh[k] = dh;
k++;
}
}
if (t == 0) {
for (i = 0; i < 53; i++) {
for (j = 0;; j++) {
if (eh[j] == Huffman[i][j]) {//将此 eh 与标准的事先存放好的编码比对
if (j + 1 == k)
break;
continue;
} else
break;
}
if (eh[j] == Huffman[i][j])
break;//循环结束,找到 eh 字符串在 Huffman 数组中所在行 i
}
if (i >= 0 && i <= 25)
fputc('a' + i, fp4);
else if (i >= 26 && i <= 51)
fputc('A' + i - 26, fp4);
else if (i == 52)
fputc(' ', fp4);
t = m;
k = 0;
goto label;
}
}
fclose(fp3);
fclose(fp4);
}
六、运行示例
输入示例:Hello world
七、实验代码
//FileName:Huffman.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define OK 1
#define ERROR 0
typedef int Status;
typedef char** HuffmanCode;
typedef struct
{
int weight;
int parent;
int lchild;
int rchild;
}HTNode, *HuffmanTree;
Status Puta()
{
int i = 0, j = 0;
char ch[6][1000];
for (i = 0; i < 6; i++)
{
fgets(ch[i], sizeof(ch[i]), stdin);
}
FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "w");
while (j < i)
{
fputs(ch[j], fp);
j++;
}
fclose(fp);
return OK;
}
Status GetWeight(int *w)
{
FILE* fp = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
if (fp == NULL)
{
printf("OPEN error!");
return ERROR;
}
char ch; int i, j;
while ((ch = fgetc(fp)) != EOF)
{
i = 0; j = 0;
if (ch == '\0');
else if (ch == ' ') { w[52]++; continue; }
else if ((int)ch >= 97 && (int)ch <= 122)
{
while (i < 26)
{
if (ch == 'a' + i) { w[i]++; break; }
i++;
}
}
else if ((int)ch >= 65 && (int)ch <= 90)
{
i = 26;
while (i < 52)
{
if (ch == 'A' + j) { w[i]++; break; }
i++;
j++;
}
}
}
i = 0; int n = 0;
for (i = 0; i < 52; i++)
{
if (w[i] != 0)n++;
}
fclose(fp);
return n;
}
Status ReadW(int* w)
{
int sum = 0, i = 0, j = 0, cnt = 0;
while (i < 52)
{
sum = sum + w[i];
i++;
}
i = 0;
printf("This pasage has %d words\n", sum);
while (i < 26)
{
printf("%c: %d ", 'a' + i, w[i]);
cnt++;
i++;
if (cnt == 4) { cnt = 0; printf("\n"); }
}
cnt = 0;
printf("\n");
while (i < 52)
{
printf("%c: %d ", 'A' + j, w[i]);
i++;
j++;
cnt++;
if (cnt == 4) { cnt = 0; printf("\n"); }
}
return OK;
}
Status Select(HuffmanTree HT, int total, int &s1, int &m2)
{
int min;
for (int i = 1; i <= total; i++)
{
if (HT[i].parent == 0) { min = HT[i].weight; s1 = i; }
else continue;
i = s1 + 1;
while (i <= total)
{
if (min > HT[i].weight&& HT[i].parent == 0) { min = HT[i].weight; s1 = i; }
i++;
}
break;
}
for (int i = 1; i <= total; i++)
{
if (HT[i].parent == 0) { if (i == s1)continue; min = HT[i].weight; m2 = i; }
else continue;
i = m2 + 1;
while (i <= total)
{
if (min > HT[i].weight&& HT[i].parent == 0)
{
if (i == s1) { i++; continue; }
min = HT[i].weight;
m2 = i;
}
i++;
}
break;
}
return OK;
}
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n)
{
HuffmanTree p; int i, s1, m2, j=0;
if (n <= 1)return;
int m = 2 * n - 1;
HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode));
for (i = 1; i <= n; ++i)
{
while (w[j] == 0)
{
j++;
}
HT[i].weight = w[j];
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
j++;
}
for (; i <= m; ++i)
{
HT[i].weight = 0;
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
for (i = n + 1; i <= m; ++i)
{
Select(HT, i - 1, s1, m2);
HT[s1].parent = i;
HT[m2].parent = i;
HT[i].lchild = s1;
HT[i].rchild = m2;
HT[i].weight = HT[s1].weight + HT[m2].weight;
}
HC = (HuffmanCode)malloc((n + 1) * sizeof(char*));
char* cd = (char*)malloc(n * sizeof(char));
cd[n - 1] = '\0';
for (i = 1; i <= n; ++i)
{
int start = n - 1;
for (int c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent)
{
if (HT[f].lchild == c)cd[--start] ='0';
else cd[--start] = '1';
}
HC[i] = (char*)malloc((n - start) * sizeof(char));
strcpy(HC[i], &cd[start]);
}
free(cd);
}
void ReadHuffmanCode(HuffmanTree HT,HuffmanCode HC, int* w,int n)
{
char Huffman[53][50];
int i, j = 1, cnt = 0;
for (i = 0; i < 26; ++i)
{
if (w[i] != 0)
{
printf("%c: %s ", 'a' + i, HC[j]);
strcpy(Huffman[i], HC[j]);
j++;
cnt++;
if (cnt == 4)printf("\n");
}
}
for (i; i < 52; ++i)
{
if (w[i] != 0)
{
printf("%c: %s ", 'A' + i - 26, HC[j]);
strcpy(Huffman[i], HC[j]);
j++;
cnt++;
if (cnt == 4)printf("\n");
}
}
strcpy(Huffman[i], HC[j]);
FILE* fp2 = fopen("C:\\Users\\LENOVO\\Desktop\\a.txt", "r");
FILE* fp1 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "w");
char ch;
while ((ch = fgetc(fp2)) != EOF)
{
i = 0; j = 0;
if (ch == ' ')
{
fputs(Huffman[52], fp1);
continue;
}
else if ((int)ch >= 97 && (int)ch <= 122)
{
while (i < 26)
{
if (ch == 'a' + i) { fputs(Huffman[i], fp1); break; }
i++;
}
}
else if ((int)ch >= 65 && (int)ch <= 90)
{
i = 26;
while (i < 52)
{
if (ch == 'A' + j) { fputs(Huffman[i], fp1); break; }
i++;
j++;
}
}
}
fclose(fp1); fclose(fp2);
FILE* fp3 = fopen("C:\\Users\\LENOVO\\Desktop\\b.txt", "r");
FILE* fp4 = fopen("C:\\Users\\LENOVO\\Desktop\\c.txt", "w");
int m = 2 * n - 1; char dh, eh[53]; int t = m, k = 0; j = 0;
while ((dh = fgetc(fp3)) != EOF)
{
lable:
if (dh == '0')
{
t = HT[t].lchild;
if (t != 0)
{
eh[k] = dh;
k++;
}
}
else if (dh == '1')
{
t = HT[t].rchild;
if (t != 0)
{
eh[k] = dh;
k++;
}
}
if (t == 0)
{
for (i = 0; i < 53; i++)
{
for (j = 0;; j++)
{
if (eh[j] == Huffman[i][j]) { if (j + 1 == k)break; continue; }
else break;
}
if (eh[j] == Huffman[i][j])break;
}
if (i >= 0 && i <= 25)fputc('a' + i, fp4);
else if (i >= 26 && i <= 51)fputc('A' + i - 26, fp4);
else if (i == 52)fputc(' ', fp4);
t = m;k = 0;
goto lable;
}
}
fclose(fp3); fclose(fp4);
}
int main()
{
int kind; HuffmanTree HT; HuffmanCode HC;
int w[55] = { 0 };
Puta();
kind = GetWeight(w) + 1;
ReadW(w);
HuffmanCoding(HT, HC, w, kind);
printf("\n\nEvery Word's HuffmanCode: \n");
ReadHuffmanCode(HT, HC, w, kind);
return 0;
}
八、算法测试结果
1. 输入:Hello world (输入时最后需要空格一下)-------当时没解决的bug,现在我也懒得看了,反正知道哈夫曼树怎么来的就行了,,,(doge
2. a文件:
3. b文件:
4. c文件:
九、分析与总结
9.1 算法复杂度分析及优、缺点分析
- 由于在 ReadHuffmanCode 有双重循环,对于 n 个字符的输入,每个字符都会 有一次双重循环,所以时间复杂度O(n3)。
- 此代码可以解决较长字符的运算以及输出,代码容易理解;但是略有繁冗,而且在 ReadHuffmanCode 函数中,在译码时,由于没有更好的办法解决文件指针后移情况,使用了 goto 语句,略有破坏代码的可读性;
- 并且在输入英文短文时,不能输入标点符号,而且在输入结束后,需要额外输入一个无用字符来弥补译码时的文件指针问题,以上均是代码的不足。
9.2 实验总结
- 熟悉了 Huffman 树的创建,以及对文件操作的理解;
- 在解决输入多行数据时遇到了困难,最后采用了 fgets 输入;
- Huffman 树,只有度为 0 或者 2 的节点,所以不存在某个字符的编码是另一个字符的前缀,这是本代码运行的前提;但在译码时使用了 goto 语句,使可读性变差;
- 可以在 Huffman 结构体中新增一个 char data 成员,用来储存字母,以减少遍历次数。
ps: 好累啊,,,赶紧溜~~