Huffman编/译码器
【问题描述】
利用Huffman编码进行通信可以大大提高信道利用率.缩短信息传输时间,降低传输成本,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个Huffman码的编/译码系统。
【基本要求】
一个完整的系统应具有以下功能:
(l)I:初始化。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。
(2)E:编码。利用已建好的Huffman树(如不在内存,则从文件hfmTree中读入),对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3)D:译码。利用已建好的Huffman树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
(4)P:印代码文件。将文件CodeFile以紧凑格式显示在终端上,每行50 个代码。
(5)T:印哈夫曼树。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
【输入输出】
(l)利用教材中的数据或自行选择一份英文文本材料调试程序。
(2)用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的编码和译码:"THIS PROGRAM IS MY FAVORITE"。
字符 |
| A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z |
频度 | 186 | 64 | 13 | 22 | 32 | 103 | 21 | 15 | 47 | 57 | 1 | 5 | 32 | 20 | 57 | 63 | 15 | 1 | 48 | 51 | 80 | 23 | 8 | 18 | 1 | 16 | 1 |
【实现提示】
(1)文件CodeFile的基类型可以设为子界型bit=0..1。
(2)用户界面可以设计为“菜单”方式:显示上述功能符号,再加上“Q”,表示退出运行Quit。请用户键入一个选择功能符。此功能执行完毕后再显示此菜单,直至某次用户选择了“Q”为止。
(3)在程序的一次执行过程中,第一次执行I,D或C命令之后,Huffman树已经在内存了,不必再读入。每次执行中不一定执行I命令,因为文件hfmTree可能早已建好。
【选做内容】
(1)上述CodeFile的基类型实际上可能占用了存放一个整数的空间,只起到示意或模拟的作用。现使CodeFile的基类型package=integer,把Huffman码紧缩到一个整形变量中去,最大限度地利用码点存储能力,试改写此系统。
(2)修改系统,实现对系统的原程序的编码和译码(主要是行尾符编/译码问题)。
(3)实现各个转换操作的源/目文件均由用户在选择此操作时指定。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<math.h>
typedef struct {
int weight;
char ch;
int parent, lchild, rchild;
}HTNode, * HuffmanTree;//动态分配数组存储哈夫曼树
typedef char** HuffmanCode; //存储哈夫曼编码
//定义全局变量
HuffmanTree HT;
int n = 0;
HuffmanCode HC;
int* w;
char* ch;
void welcome(); //操作选择界面
void select(HuffmanTree HT, int j, int& s1, int& s2); //选择parent为0且weight最小的两个结点
void Init(); //输入字符及其权值,建立哈夫曼树
void Encoding(); //编码
void Decoding(); //译码
void Tree_printing(); //打印哈夫曼树
void Print();//打印代码
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n, char* ch);
void Free();//释放动态分配空间
void welcome()
{
printf("**************************************\n");
printf("* 请选择如下操作 *\n");
printf("* I.初始化 *\n");
printf("* E.编码 *\n");
printf("* D.译码 *\n");
printf("* P.印代码文件 *\n");
printf("* T.打印哈夫曼树 *\n");
printf("* Q.退出 *\n");
printf("**************************************\n");
}
//输入字符及其权值,建立哈夫曼树
void Init()
{
FILE* fp;
int i;
printf("请输入字符个数:\n");
scanf("%d", &n);
while (n < 2) {
printf("输入的数据有误,请重新输入:\n");
scanf("%d", &n);
}
ch = (char*)malloc(n * sizeof(char));
printf("请输入所有字符:\n");
getchar();
for (i = 0; i < n; ++i)
scanf("%c", &ch[i]);
w = (int*)malloc(n * sizeof(int));
printf("请输入这些字符的权值:\n");
for (i = 0; i < n; ++i)
scanf("%d", &w[i]);
HuffmanCoding(HT, HC, w, n, ch);
/*printf("编码结果如下:\n");
for(i=1;i<=n;++i)
printf("%c:%s\n",HT[i].ch,HC[i]);*///调试
fp = fopen("hfmTree.txt", "w");
fprintf(fp, "%d", n);
for (i = 0; i < n; ++i)
fprintf(fp, "%c", ch[i]);
for (i = 0; i < n; ++i)
fprintf(fp, "%5d", w[i]);
for (i = 1; i <= n; ++i)
fprintf(fp, "\n%c:%s", HT[i].ch, HC[i]);
fclose(fp);
printf("初始化成功\n");
}
//选择parent为0且weight最小的两个结点
void select(HuffmanTree HT, int j, int& s1, int& s2)
{
int i;
s1 = -1;
for (i = 1; i <= j; i++)
if (HT[i].parent == 0) {
if ((s1 == -1) || (HT[i].weight < HT[s1].weight)) {
s1 = i;
}
HT[s1].parent = 1;
s2 = -1;
for (i = 1; i <= j; i++)
if (HT[i].parent == 0) {
if ((s2 == -1) || (HT[i].weight < HT[s2].weight)) {
s2 = i;
}
HT[s2].parent = 1;
}
}
}
void HuffmanCoding(HuffmanTree& HT, HuffmanCode& HC, int* w, int n, char* ch){
int m;
m = 2 * n - 1;//一共2n-1个节点
int s1, s2;
HT = (HTNode*)malloc((m + 1) * sizeof(HTNode));//开辟节点空间
HTNode* p;
int i;
for (p = HT + 1, i = 1; i <= n; ++p, ++i, ++w, ++ch) {
p->ch = *ch;
p->weight = *w;
p->parent = p->lchild = p->rchild = 0;//向节点中填入字符,权值,以及双亲,孩子的标识
}
for (; i <= m; ++i, ++p) {
p->ch = 0;
p->weight = 0;
p->parent = p->lchild = p->rchild = 0;//初始化第n个以后的节点
}
for (i = n + 1; i <= m; ++i) {//从第n+1个开始
select(HT, i - 1, s1, s2);//从前n个中找具有最小的权值的两个字符,i作为其双亲
HT[s1].parent = i; HT[s2].parent = i;
HT[i].lchild = s1; HT[i].rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
HC = (char**)malloc((n + 1) * sizeof(char*));//哈夫曼码
char* cd;
cd = (char*)malloc(n * sizeof(char));
cd[n - 1] = '\0';
int total = 0;
int c, f;
for (i = 1; i <= n; ++i) {
for (c = i, f = HT[i].parent; f != 0; c = f, f = HT[f].parent) {
if (HT[f].lchild == c) { cd[total++] = '0'; }
else cd[total++] = '1';
}
HC[i] = (char*)malloc((total) * sizeof(char));
int j;
for (j = total - 1; j >= 0; j--) HC[i][total - 1 - j] = cd[j];
}
free(cd);
}
//编码
void Encoding()
{
FILE* fp, * fp1;
int i;
char in;
if (n == 0)
{
fp = fopen("hfmTree.txt", "r");
fscanf(fp, "%d", &n);
if (feof(fp)) {
printf("请先初始化哈夫曼树!\n");
}
ch = (char*)malloc(n * sizeof(char));
for (i = 0; i < n; ++i)
fscanf(fp, "%c", &ch[i]);
w = (int*)malloc(n * sizeof(int));
for (i = 0; i < n; ++i)
fscanf(fp, "%d", &w[i]);
HuffmanCoding(HT, HC, w, n, ch);
printf("已载入哈夫曼树!\n");
fclose(fp);
}
fp = fopen("ToBeTran.txt", "r");
printf("成功读取ToBeTran.txt\n");
fp1 = fopen("CodeFile.txt", "w");
fscanf(fp, "%c", &in);//读取一个字符
while (!feof(fp))
{
for (i = 1; i <= n; ++i) {
if (HT[i].ch == in)//循环直至找到目标字符所在位置
break;
}
fprintf(fp1, "%s", HC[i]);//把哈夫曼码写入文件
fscanf(fp, "%c", &in);//再读取一个字符
}
printf("\n编码结束,结果已存入CodeFile.txt文件中!\n");
fclose(fp);
fclose(fp1);
}
//译码
void Decoding()
{
FILE* fp, * fp1;
int i, m;
char in;
if (n == 0)
{
fp = fopen("hfmTree.txt", "r");
fscanf(fp, "%d", &n);
if (feof(fp)) {
printf("请先初始化哈夫曼树!\n");
}
ch = (char*)malloc(n * sizeof(char));
for (i = 0; i < n; ++i)
fscanf(fp, "%c", &ch[i]);
w = (int*)malloc(n * sizeof(int));
for (i = 0; i < n; ++i)
fscanf(fp, "%d", &w[i]);
HuffmanCoding(HT, HC, w, n, ch);
printf("已载入哈夫曼树!\n");
fclose(fp);
}
fp = fopen("CodeFile.txt", "r");
fp1 = fopen("TextFile.txt", "w");
for (i = 1; HT[i].parent != 0; ++i);//找到根节点
m = i;//根节点编号赋值给m
while (!feof(fp)) {
if (HT[m].lchild && HT[m].rchild) {
fscanf(fp, "%c", &in);//读取被编码的文件的1位
if (in == '0') m = HT[m].lchild;//如果为0,则把此节点的左孩子赋值给m,直至终端节点
else m = HT[m].rchild;
}
else {
fprintf(fp1, "%c", HT[m].ch);//把解码出来的字符写入文件
m = i;
}
}
printf("\n译码结束,译码结果已保存到TextFile.txt文件中!\n");
fclose(fp);
fclose(fp1);
}
// 打印代码
void Print()
{
FILE* fp;
int i = 0;
char in;
fp = fopen("CodeFile.txt", "r");
//fp1 = fopen("CodePrin.txt", "w");
fscanf(fp, "%c", &in);
while (!feof(fp))
printf("代码文件为:\n");
{
printf("%c", in);
//fprintf(fp1, "%c", in);
i++;
if (i == 50) {
printf("\n");
//fprintf(fp1, "\n");
i = 0;
}
fscanf(fp, "%c", &in);
}
printf("\n");
fclose(fp);
//fclose(fp1);
//printf("代码文件已存入CodePrin.txt文件中");
printf("\n");
}
//打印哈夫曼树
void Tree_printing()
{
FILE* fp;
fp = fopen("TreePrint.txt", "w");
int i;
printf("字符 权值 双亲 左孩子 右孩子\n");
for (i = 1; i < 2 * n; i++) {
printf("%c%8d%8d%8d%8d\n", HT[i].ch, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
fprintf(fp, "%c%8d%8d%8d%8d", HT[i].ch, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
fprintf(fp, "\n");
}
fclose(fp);
printf("哈夫曼树已存入TreePrint.txt\n");
}
//释放动态分配空间
void Free()
{
free(HT);
free(HC);
free(w);
free(ch);
}
int main()
{
char choice;
while (1)
{
welcome();
scanf("%c", &choice);
switch (choice)
{
case 'i':
case 'I':Init(); break;
case 'e':
case 'E':Encoding(); break;
case 'd':
case 'D':Decoding(); break;
case 'p':
case 'P':Print(); break;
case 't':
case 'T':Tree_printing(); break;
case 'q':
case 'Q':Free(); exit(1);
default:printf("Input error!\n");
}
getchar();
}
return 0;
}