哈夫曼编码/译码器。利用哈夫曼编码进行信息通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼编/译码系统。
(1)初始化(Initialization)。从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。
(2)编码(Encoding)。利用已建好的哈夫曼树,对文件ToBeTran中的正文进行编码,然后将结果存入文件CodeFile中。
(3)译码(Decoding)。利用已建好的哈夫曼树将文件CodeFile中的代码进行译码,结果存入文件TextFile中。
(4)打印代码文件(Print)。将文件CodeFile以紧凑格式显示在终端上,每行50个代码。同时将此字符形式的编码写入文件CodePrint中。
(5)打印哈夫曼树(Tree Printing)。将已在内存中的哈夫曼树以直观的方式(树或凹入表形式)显示在终端上,同时将此字符形式的哈夫曼树写入文件TreePrint中。
(1)已知某系统在通信联络中只可能出现8种字符,其概率分别为0.05,0.29,0.07,0.08,0.14,0.23,0.03,0.11,以此设计哈夫曼编码。利用此数据对程序进行调试。
(2)用下表给出的字符集和频度的实际统计数据建立哈夫曼树,并实现以下报文的编码和译码:“THIS PROGRAM IS MY FAVORITE”。
#pragma warning(disable:4996)
#include
#include
#include
#define maxsize 100
#define N 100 //叶子结点最大个数
#define M 2*N-1 //所有结点的最大值
typedef struct Node
{
int L_child;
int R_child;
int weight;//结点的权值
int parent;//双亲下标
char ch;
}Node, HuffmanTree[M + 1];
typedef char* HuffmanCode[N + 1];
FILE* fp1;//从终端读入字符集大小n,以及n个字符和n个权值,建立哈夫曼树,并将它存于文件hfmTree中。
FILE* fp2;//ToBeTran中的正文
FILE* fp3;//哈夫曼编码结果存在CodeFile中。
FILE* fp4;//译码结果存入文件TextFile中
FILE* fp5;//字符形式的编码
FILE* fp6;// =fopen("D:\\123.txt", "w");//字符形式的哈夫曼树写入文件TreePrint中
void select(HuffmanTree huffmanTree, int n, int* s1, int* s2)
{
int i = 0;
int min;//记录最小权值
for (i = 1; i <= n; i++)
{
if (huffmanTree[i].parent == 0)//如果此结点的没有父亲,那么把结点号赋值给 min,跳出循环
{
min = i;
break;
}
}
for (i = 1; i <= n; i++)
{
if (huffmanTree[i].parent == 0)//如果此结点的父亲为空,则进入 if
{
if (huffmanTree[i].weight < huffmanTree[min].weight)//如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
min = i;
}
}
*s1 = min;//找到了最小权值的结点,s1指向
for (i = 1; i <= n; i++)//遍历全部结点
{
if (huffmanTree[i].parent == 0 && i != (*s1))//找出下一个单节点,且没有被 s1指向,那么i 赋值给 min,跳出循环
{
min = i;
break;
}
}
for (i = 1; i <= n; i++)//继续遍历全部结点,找到权值最小的那一个
{
if (huffmanTree[i].parent == 0 && i != (*s1))
{
if (huffmanTree[i].weight < huffmanTree[min].weight)//如果此结点的权值比 min 结点的权值小,那么更新 min 结点,否则就是最开始的 min
min = i;
}
}
*s2 = min;//s2指针指向第二个权值最小的叶子结点
}
void CrtHuffmanTree(HuffmanTree HuffTree, int n)//创建哈夫曼树
{
int i, m, j;
int s1, s2;
printf("请输入字符及对应权值:\n");
//memset("D:\\hfmTree.txt", 0, sizeof("D:\\hfmTree.txt"));
// sprintf("D:\\hfmTree.txt", "D:\\hfmTree.txt");//写入你要打开的文件名
fp1 = fopen("D:\\hfmTree.txt", "w");
for (i = 1; i <= n; i++)
{
fflush(stdin);//清除缓存区
scanf("%c%d", &HuffTree[i].ch, &HuffTree[i].weight);
HuffTree[i].parent = 0;
HuffTree[i].L_child = 0;
HuffTree[i].R_child = 0;
fprintf(fp1, "%c\t%d\n", HuffTree[i].ch, HuffTree[i].weight);
}
fclose(fp1);
m = 2 * n - 1;
for (i = n + 1; i <= m; i++)//非叶结点的初始化
{
HuffTree[i].L_child = 0;
HuffTree[i].R_child = 0;
HuffTree[i].weight = 0;
HuffTree[i].parent = 0;
}
for (i = n + 1; i <= m; i++)
{
select(HuffTree, i - 1, &s1, &s2);
HuffTree[i].weight = HuffTree[s1].weight + HuffTree[s2].weight;
HuffTree[s1].parent = i;
HuffTree[s2].parent = i;
HuffTree[i].L_child = s1;
HuffTree[i].R_child = s2;
}
printf("建立完成后:\n");
for (i = 1; i <= n; i++)
{
printf("%c\t%d\n", HuffTree[i].ch, HuffTree[i].weight);
}
}
int ReadFile(HuffmanTree HuffTree, int n)
{
char ch;
int i, m, j,head=n;
int s1, s2;
fp1 = fopen("D:\\hfmTree.txt", "r");
for (i = 1; i <= n; i++)
{
fflush(stdin);//清除缓存区
fscanf(fp1, "%c\t%d\n", &HuffTree[i].ch, &HuffTree[i].weight);
HuffTree[i].parent = 0;
HuffTree[i].L_child = 0;
HuffTree[i].R_child = 0;
}
fclose(fp1);
m = 2 * n - 1;
for (i = n + 1; i <= m; i++)//非叶结点的初始化
{
HuffTree[i].L_child = 0;
HuffTree[i].R_child = 0;
HuffTree[i].weight = 0;
HuffTree[i].parent = 0;
HuffTree[i].ch = '*';
}
for (i = n + 1; i <= m; i++)
{
select(HuffTree, i - 1, &s1, &s2);
HuffTree[i].weight = HuffTree[s1].weight + HuffTree[s2].weight;
HuffTree[s1].parent = i;
HuffTree[s2].parent = i;
HuffTree[i].L_child = s1;
HuffTree[i].R_child = s2;
}
printf("建立完成后:\n");
for (i = 1; i <= n; i++)
{
printf("%c\t%d\n", HuffTree[i].ch, HuffTree[i].weight);
}
while (HuffTree[head].parent != 0 )
{
head++;
}
printf("哈夫曼树的根节点为:%d\n", head);
return head;
}
void CrtHuffmanCode(HuffmanTree ht, HuffmanCode hc, int n)
{
int start, i, c, p;
char* cd;
cd = (char*)malloc(n * sizeof(char));
cd[n - 1] = '\0';
for (i = 1; i <= n; i++)
{
start = n - 1;
c = i;
p = ht[i].parent;
while (p != 0)
{
--start;
if (ht[p].L_child == c)cd[start] = '0';
else cd[start] = '1';
c = p;
p = ht[p].parent;
}
hc[i] = (char*)malloc((n - start) * sizeof(char));
strcpy(hc[i], &cd[start]);
}
free(cd);
printf("编码为:\n");
for (i = 1; i <= n; i++)
{
printf("%c\t%s\n", ht[i].ch, hc[i]);
}
}
void Queryman(char Midchar[1000], HuffmanTree ht, HuffmanCode hc, int m, int n)
{
int i = 1, j = 1;
char ch, Mid;
fp3 = fopen("D:\\CodeFile.txt", "w");
if (fp3 == NULL)printf("文件打开失败");
while (i < m)
{
j = 1;
ch = Midchar[i];
while (ch != ht[j].ch && j < n)
{
j++;
}
fprintf(fp3, "%s", hc[j]);
i++;
}
rewind(fp3);
fclose(fp3);
}
void Encoding(HuffmanTree ht, HuffmanCode hc, int n)
{
int i = 0, m = 0;
char Midchar[1000];
char mid=0, ch;
fp2 = fopen("D:\\ToBeTran.txt", "r");
if (fp2 == NULL)printf("文件打开失败");
else
{
while (!feof(fp2))
{
// if(ferror(fp2))clearerr(fp2);
Midchar[i] = mid;
m++;
printf("%c", Midchar[i]);
i++;
mid = fgetc(fp2);
}
printf("\n");
rewind(fp2);//将指针重新置于文件开头
fclose(fp2);
Queryman(Midchar, ht, hc, m, n);
}
}
void Printing()
{
char m=0;
int i = 0;
fp3 = fopen("D:\\CodeFile.txt", "r");
if (fp3 == NULL)printf("文件打开失败");
else
{
fp5 = fopen("D:\\CodePrint.txt", "w");
if (fp5 == NULL)printf("文件打开失败");
else
{
m = fgetc(fp3);
while (!feof(fp3))
{
if (i == 50)
{
printf("\n");
fprintf(fp5,"\n");
i = 0;
}
printf("%c", m);
fprintf(fp5, "%c", m);
i++;
m = fgetc(fp3);
}
rewind(fp5);
fclose(fp5);
}
printf("\n");
rewind(fp3);
fclose(fp3);
}
}void transcodehuffman(HuffmanTree HuffTree, int n, int head)
{
int i = head;
char mid,L,R;
fp3 = fopen("D:\\CodeFile.txt", "r");
if (fp3 == NULL)printf("文件打开失败");
else
{
fp4 = fopen("D:\\TextFile.txt", "w");
if (fp3 == NULL)printf("文件打开失败");
else
{
printf("译码结果为:\n");
while (!feof(fp3))
{
mid = fgetc(fp3);
if (mid == '0') i = HuffTree[i].L_child;
else i = HuffTree[i].R_child;
if (HuffTree[i].L_child == 0 && HuffTree[i].R_child == 0)
{
printf("%c", HuffTree[i].ch);
fprintf(fp4, "%c", HuffTree[i].ch);
i = head;
}
}
}
rewind(fp4);
fclose(fp4);
}
rewind(fp3);
fclose(fp3);
printf("\n");
}
int TreeDepth(HuffmanTree ht, int m)
{
int hl, hr, max;
if (ht[m].L_child != 0 || ht[m].R_child != 0)
{
hl = TreeDepth(ht, ht[m].L_child);
hr = TreeDepth(ht, ht[m].R_child);
max = hl > hr ? hl : hr;
return(max + 1);
}
else return 0;
}
void TreePrint(HuffmanTree HuffTree, int m, int nLayer)
{
int i = 0;
// fp6 = fopen("D:\\Tree Printing.txt", "w");
if (fp6 == NULL)printf("文件打开失败");
else
{
if (HuffTree[m].L_child == 0 || HuffTree[m].R_child == 0)
{
for (i = 0; i < nLayer; i++)
{
fprintf(fp6, "%s", " ");
printf(" ");
}
if (HuffTree[m].ch == 32)
{
fprintf(fp6, "%c\n", '#');
printf("#\n");
}
else {
fprintf(fp6, "%c\n", HuffTree[m].ch);
printf("%c\n", HuffTree[m].ch);
}
return;
}
TreePrint(HuffTree, HuffTree[m].R_child, nLayer + 1);
for (i = 0; i < nLayer; i++)
{
fprintf(fp6, "%s", " ");
printf(" ");
}
if (HuffTree[m].ch == '*')
{
fprintf(fp6, "%d\n", HuffTree[m].weight);
printf("%d\n", HuffTree[m].weight);
}
else
{
if (HuffTree[m].ch == ' ')
{
fprintf(fp6, "%c\n", "*");
printf("*\n");
}
fprintf(fp6, "%c\n", HuffTree[m].ch);
printf("%c\n", HuffTree[m].ch);
}
TreePrint(HuffTree, HuffTree[m].L_child, nLayer + 1);
}
}
void menu()
{
printf("====================================================\n");
printf("|| ||\n");
printf("|| ||\n");
printf("|| 1.从终端读入字符及权值 ||\n");
printf("|| ||\n");
printf("|| ||\n");
printf("|| 2.从文件读入字符及权值 ||\n");
printf("|| ||\n");
printf("|| ||\n");
printf("|| 3.结束程序 ||\n");
printf("|| ||\n");
printf("|| ||\n");
printf("====================================================\n");
}
int main()
{
int n;//字符集的大小
int head=0;//哈夫曼树根
char ch;
HuffmanTree HuffTree;
HuffmanCode hc;
while (1)
{
menu();
printf("请选哈夫曼树择构建方式:\n");
fflush(stdin);//清除缓存区
scanf("%d", &ch);
switch (ch)
{
case 1:
{
printf("请输入字符集大小n:");
scanf("%d", &n);
CrtHuffmanTree(HuffTree, n);
CrtHuffmanCode(HuffTree, hc, n);
Encoding(HuffTree, hc, n);
Printing();
transcodehuffman(HuffTree, n, head);
printf("\n%d\n", TreeDepth(HuffTree, head));
TreePrint(HuffTree, head, TreeDepth(HuffTree, head));
break;
}
case 2:
{
printf("请输入字符集大小n:");
scanf("%d", &n);
head = ReadFile(HuffTree, n);
CrtHuffmanCode(HuffTree, hc, n);
Encoding(HuffTree, hc, n);
Printing();
transcodehuffman(HuffTree, n, head);
printf("\n%d\n", TreeDepth(HuffTree, head));
fp6=fopen("D:\\Tree Printing.txt", "w");
TreePrint(HuffTree, head, TreeDepth(HuffTree, head));
break;
}
case 3:
{
return 0;
break;
}
default:
{
printf("输入不合法\n"); break;
}
}
}
}
a.问题分析与解决
(1)在调试时由于多次调用同一个文件并没有将指针复位,导致出现乱码。利用rewind( )函数可以将文件指针复位到文件开头,可以解决这个问题。
(2)当遇到需要读入字符且换行多次读取字符时,scanf( )函数会将换行符也读入,导致出现乱码。调用 库中的cin函数不仅可以自动吞掉换行符,而且可以不限输入格式。也可以在scanf( )函数后调用getchar( )函数来消除回车换行符。
(3)在使用fopen()函数时,一定要在写清文件的后缀,否则可能无法找到文件。
(4)在读取文件时,不可轻易使用rewind( )函数,否则读取文件数据时会把换行符也读入,导致读入错误而产生乱码。
b.算法的时空分析与改进设想
(1)在求解二叉树的深度时,我采用了后序遍历递归算法,在树的深度很大时其时空性能不高。可以通过建立一个显式栈来提高性能。
(2)本次采用的是静态链表构造的哈夫曼树,开辟的空间较大。个人认为利用动态链表构造哈夫曼树更能节省空间。