设计实现一个基于哈夫曼算法的压缩软件,统计文件中各字符出现的频次,设计哈夫曼编码,用c语言写出完整代码并写出详细注释
时间: 2024-05-08 08:21:06 浏览: 156
首先,我们需要了解哈夫曼编码的基本原理。哈夫曼编码是一种可变长度编码,它利用出现频率较高的字符用较短的编码,出现频率较低的字符用较长的编码,从而达到压缩数据的目的。
实现哈夫曼编码,我们需要以下几个步骤:
1. 统计文件中各字符出现的频次
2. 根据频次构建哈夫曼树
3. 从根节点出发,向左走为0,向右走为1,构建哈夫曼编码表
4. 使用哈夫曼编码表对文件进行编码
5. 将编码后的文件进行解码,得到原始文件
下面是基于哈夫曼算法的压缩软件的代码实现:
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_TREE_HT 100
#define MAX_FILENAME_SIZE 100
// 结构体:哈夫曼树
struct HuffmanTree
{
char data; // 节点存储的字符数据
int freq; // 节点出现的频次
struct HuffmanTree *left; // 左子节点
struct HuffmanTree *right; // 右子节点
};
// 结构体:哈夫曼编码表
struct HuffmanTable
{
char data; // 字符数据
char code[MAX_TREE_HT]; // 哈夫曼编码
int len; // 编码长度
};
// 函数:统计文件中各字符出现的频次
void getFrequency(FILE *fp, int frequency[])
{
char c;
while ((c = fgetc(fp)) != EOF)
{
frequency[c]++;
}
}
// 函数:构建哈夫曼树
struct HuffmanTree* buildHuffmanTree(int frequency[])
{
int i;
struct HuffmanTree *node, *left, *right;
struct HuffmanTree *queue[MAX_TREE_HT], *temp;
// 初始化队列
for (i = 0; i < MAX_TREE_HT; i++)
{
queue[i] = NULL;
}
// 将所有出现频次的字符作为叶子节点,加入队列中
for (i = 0; i < 256; i++)
{
if (frequency[i] > 0)
{
node = (struct HuffmanTree*) malloc(sizeof(struct HuffmanTree));
node->data = i;
node->freq = frequency[i];
node->left = NULL;
node->right = NULL;
queue[i] = node;
}
}
// 构建哈夫曼树
while (1)
{
// 从队列中找出频次最小的两个节点
left = NULL;
right = NULL;
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] != NULL)
{
if (left == NULL || queue[i]->freq < left->freq)
{
left = queue[i];
}
if (right == NULL || queue[i]->freq < right->freq)
{
right = queue[i];
}
}
}
// 将找出的两个节点合并成一个新的节点
node = (struct HuffmanTree*) malloc(sizeof(struct HuffmanTree));
node->data = 0;
node->freq = left->freq + right->freq;
node->left = left;
node->right = right;
// 将新节点加入队列
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] == NULL)
{
queue[i] = node;
break;
}
}
// 如果队列中只剩下一个节点,说明哈夫曼树构建完成
if (i == 1)
{
break;
}
}
// 返回根节点
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] != NULL)
{
return queue[i];
}
}
return NULL;
}
// 函数:从根节点出发,向左走为0,向右走为1,构建哈夫曼编码表
void buildHuffmanTable(struct HuffmanTree *node, struct HuffmanTable table[], int index, char code[], int len)
{
if (node->left == NULL && node->right == NULL)
{
table[index].data = node->data;
strcpy(table[index].code, code);
table[index].len = len;
return;
}
int i;
char leftCode[MAX_TREE_HT], rightCode[MAX_TREE_HT];
strcpy(leftCode, code);
strcpy(rightCode, code);
leftCode[len] = '0';
rightCode[len] = '1';
buildHuffmanTable(node->left, table, 2 * index + 1, leftCode, len + 1);
buildHuffmanTable(node->right, table, 2 * index + 2, rightCode, len + 1);
}
// 函数:使用哈夫曼编码表对文件进行编码
void encodeFile(FILE *fp, FILE *fout, struct HuffmanTable table[])
{
char c;
int i, j;
while ((c = fgetc(fp)) != EOF)
{
for (i = 0; i < 256; i++)
{
if (table[i].data == c)
{
for (j = 0; j < table[i].len; j++)
{
fputc(table[i].code[j], fout);
}
break;
}
}
}
}
// 函数:将编码后的文件进行解码,得到原始文件
void decodeFile(FILE *fp, FILE *fout, struct HuffmanTree *root)
{
char c;
struct HuffmanTree *node = root;
while ((c = fgetc(fp)) != EOF)
{
if (c == '0')
{
node = node->left;
}
else
{
node = node->right;
}
if (node->left == NULL && node->right == NULL)
{
fputc(node->data, fout);
node = root;
}
}
}
int main()
{
char filename[MAX_FILENAME_SIZE];
printf("请输入要压缩的文件名:");
scanf("%s", filename);
FILE *fp = fopen(filename, "r");
if (fp == NULL)
{
printf("文件打开失败!");
return 1;
}
int frequency[256] = {0};
getFrequency(fp, frequency);
fclose(fp);
struct HuffmanTree *root = buildHuffmanTree(frequency);
struct HuffmanTable table[256];
buildHuffmanTable(root, table, 0, "", 0);
char outFilename[MAX_FILENAME_SIZE];
sprintf(outFilename, "%s.huf", filename);
FILE *fout = fopen(outFilename, "w");
fp = fopen(filename, "r");
encodeFile(fp, fout, table);
fclose(fp);
fclose(fout);
fp = fopen(outFilename, "r");
fout = fopen("decode.txt", "w");
decodeFile(fp, fout, root);
fclose(fp);
fclose(fout);
return 0;
}
```
注释详解:
1. 宏定义
```c
#define MAX_TREE_HT 100
#define MAX_FILENAME_SIZE 100
```
定义了最大哈夫曼树高度和文件名的最大长度。
2. 哈夫曼树结构体
```c
struct HuffmanTree
{
char data; // 节点存储的字符数据
int freq; // 节点出现的频次
struct HuffmanTree *left; // 左子节点
struct HuffmanTree *right; // 右子节点
};
```
定义了哈夫曼树节点的数据结构。
3. 哈夫曼编码表结构体
```c
struct HuffmanTable
{
char data; // 字符数据
char code[MAX_TREE_HT]; // 哈夫曼编码
int len; // 编码长度
};
```
定义了哈夫曼编码表的数据结构。
4. 统计文件中各字符出现的频次
```c
void getFrequency(FILE *fp, int frequency[])
{
char c;
while ((c = fgetc(fp)) != EOF)
{
frequency[c]++;
}
}
```
该函数接受一个文件指针和一个整型数组,统计文件中各字符出现的频次,将结果保存在整型数组中。
5. 构建哈夫曼树
```c
struct HuffmanTree* buildHuffmanTree(int frequency[])
{
int i;
struct HuffmanTree *node, *left, *right;
struct HuffmanTree *queue[MAX_TREE_HT], *temp;
// 初始化队列
for (i = 0; i < MAX_TREE_HT; i++)
{
queue[i] = NULL;
}
// 将所有出现频次的字符作为叶子节点,加入队列中
for (i = 0; i < 256; i++)
{
if (frequency[i] > 0)
{
node = (struct HuffmanTree*) malloc(sizeof(struct HuffmanTree));
node->data = i;
node->freq = frequency[i];
node->left = NULL;
node->right = NULL;
queue[i] = node;
}
}
// 构建哈夫曼树
while (1)
{
// 从队列中找出频次最小的两个节点
left = NULL;
right = NULL;
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] != NULL)
{
if (left == NULL || queue[i]->freq < left->freq)
{
left = queue[i];
}
if (right == NULL || queue[i]->freq < right->freq)
{
right = queue[i];
}
}
}
// 将找出的两个节点合并成一个新的节点
node = (struct HuffmanTree*) malloc(sizeof(struct HuffmanTree));
node->data = 0;
node->freq = left->freq + right->freq;
node->left = left;
node->right = right;
// 将新节点加入队列
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] == NULL)
{
queue[i] = node;
break;
}
}
// 如果队列中只剩下一个节点,说明哈夫曼树构建完成
if (i == 1)
{
break;
}
}
// 返回根节点
for (i = 0; i < MAX_TREE_HT; i++)
{
if (queue[i] != NULL)
{
return queue[i];
}
}
return NULL;
}
```
该函数接受一个整型数组,构建哈夫曼树,并返回根节点。
6. 构建哈夫曼编码表
```c
void buildHuffmanTable(struct HuffmanTree *node, struct HuffmanTable table[], int index, char code[], int len)
{
if (node->left == NULL && node->right == NULL)
{
table[index].data = node->data;
strcpy(table[index].code, code);
table[index].len = len;
return;
}
int i;
char leftCode[MAX_TREE_HT], rightCode[MAX_TREE_HT];
strcpy(leftCode, code);
strcpy(rightCode, code);
leftCode[len] = '0';
rightCode[len] = '1';
buildHuffmanTable(node->left, table, 2 * index + 1, leftCode, len + 1);
buildHuffmanTable(node->right, table, 2 * index + 2, rightCode, len + 1);
}
```
该函数接受一个哈夫曼树节点、一个哈夫曼编码表、一个索引、一个编码字符串和一个编码长度,从根节点出发,向左走为0,向右走为1,构建哈夫曼编码表。
7. 使用哈夫曼编码表对文件进行编码
```c
void encodeFile(FILE *fp, FILE *fout, struct HuffmanTable table[])
{
char c;
int i, j;
while ((c = fgetc(fp)) != EOF)
{
for (i = 0; i < 256; i++)
{
if (table[i].data == c)
{
for (j = 0; j < table[i].len; j++)
{
fputc(table[i].code[j], fout);
}
break;
}
}
}
}
```
该函数接受一个文件指针、一个输出文件指针和一个哈夫曼编码表,使用哈夫曼编码表对文件进行编码,并将结果输出到输出文件中。
8. 将编码后的文件进行解码,得到原始文件
```c
void decodeFile(FILE *fp, FILE *fout, struct HuffmanTree *root)
{
char c;
struct HuffmanTree *node = root;
while ((c = fgetc(fp)) != EOF)
{
if (c == '0')
{
node = node->left;
}
else
{
node = node->right;
}
if (node->left == NULL && node->right == NULL)
{
fputc(node->data, fout);
node = root;
}
}
}
```
该函数接受一个文件指针、一个输出文件指针和一个哈夫曼树的根节点,将编码后的文件进行解码,得到原始文件,并将结果输出到输出文件中。
9. 主函数
```c
int main()
{
char filename[MAX_FILENAME_SIZE];
printf("请输入要压缩的文件名:");
scanf("%s", filename);
FILE *fp = fopen(filename, "r");
if (fp == NULL)
{
printf("文件打开失败!");
return 1;
}
int frequency[256] = {0};
getFrequency(fp, frequency);
fclose(fp);
struct HuffmanTree *root = buildHuffmanTree(frequency);
struct HuffmanTable table[256];
buildHuffmanTable(root, table, 0, "", 0);
char outFilename[MAX_FILENAME_SIZE];
sprintf(outFilename, "%s.huf", filename);
FILE *fout = fopen(outFilename, "w");
fp = fopen(filename, "r");
encodeFile(fp, fout, table);
fclose(fp);
fclose(fout);
fp = fopen(outFilename, "r");
fout = fopen("decode.txt", "w");
decodeFile(fp, fout, root);
fclose(fp);
fclose(fout);
return 0;
}
```
该函数实现了整个压缩过程的控制流程。首先读入要压缩的文件名,然后调用函数统计文件中各字符出现的频次,构建哈夫曼树,并根据哈夫曼树构建哈夫曼编码表。接下来,为压缩文件起一个新的文件名,打开该文件并以写入模式打开输出文件。使用哈夫曼编码表对文件进行编码,并将结果输出到输出文件中。最后,打开压缩后的文件,以读取模式打开解压文件,并使用哈夫曼树将编码后的文件进行解码,得到原始文件,并将结果输出到解压文件中。
阅读全文