ps:代码是网上找的,删了一些,只保留了编译码,可以实现文件存入读取
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
struct HuffmanTree //用于表示哈夫曼树
{
int weight;
int Lchild, Rchild, Parent;
char letter;
};
struct code //用于表示哈夫曼编码
{
char col[28]; //编码
char letter; //字符
} HC[28]; //数组HC存储这些编码
struct HuffmanTree *HT = NULL; //指向哈夫曼树的指针,被用于动态分配内存以创建哈夫曼树
int N; //表示字符集的大小
//整个哈夫曼树的结构和相关信息将通过HT指针来管理
void Input();
void Init();
void CreateHTree(char a[], int w[]);
void Select(int n, int *s1, int *s2);
void PutHuffmanTree();
void CreateHuffmanCode();
void Puteverycode();
int FILEhfmtree();
void Codetxt();
void DeCodetxt();
//用来事先声明函数,便于主函数调用
int main()
{
int i, j;
char command; //存储用户输入的命令
system("color 0f"); //设置控制台的文本颜色为默认值
while (1)
{
printf("哈夫曼编译码器\n\n");
printf("O: 输入一段字符(包含26个大写字母和空格),统计出各个字符出现的次数\n\n");
printf("I:初始化,建立哈夫曼树\n\n");
printf("E:编码(Encoding)\n\n");
printf("D:译码(Decoding)\n\n");
printf("Q:退出程序\n\n");
printf("请输入一个字母选择功能:");
scanf("%c", &command); //从用户输入中获取一个字符,存储到command变量中
fflush(stdin);
if ('Q' == command || 'q' == command)
break;
switch (command)
{
case 'o':
case 'O':
Input();
break;
case 'i':
case 'I':
Init();
break;
case 'e':
case 'E':
Codetxt();
break;
case 'd':
case 'D':
DeCodetxt();
break;
case 't':
default:
printf("输入的命令有误!\n");
system("pause");
system("cls");
break;
}
}
system("pause");
return 0;
}
void Input() //用于输入字符串,统计字符频度并将结果写入文件
{
char str[1024], a[27] = " "; //str用于存储用户输入的字符串
int Alph[27], i, j, flag = 1;//Alph用于存储字符频度,flag标志位初始化为1
FILE *fp; //文件指针fp用于文件操作
memset(Alph, 0, 27 * sizeof(int)); //将alph数组的所有元素初始化为0
printf("请输入一串必须包含26个大写英文字母和空格的字符串\n");
printf("Tip:输入以回车结束\n");
gets(str); //获取用户输入
i = 0;
while (str[i]) //统计每个字符出现的次数
{
if ('A' <= str[i] && str[i] <= 'Z')
Alph[str[i] - 'A' + 1] += 1;
else if (str[i] == ' ')
Alph[0] += 1; //将结果存储在Alph数组中
else
{
flag = 0; //检查输入合法性
printf("输入字符串有不合条件的字符,请退出后重试!\n");
break;
}
i++;
}
if (flag)
{
if (!Alph[0])
{
flag = 0;
printf("输入的字符串不包含 空格\n");
}
for (i = 1; i < 27; i++)
if (!Alph[i])
{
flag = 0;
printf("输入的字符串不包含 %c\n", i + 64);
}
if (!flag)
printf("请退出后重新输入\n");
}
if (flag)
{
fp = fopen("chartexfile.txt", "w");
if (fp == NULL)
{
printf("创建文件chartexfile.txt错误\n");
system("pause");
system("cls");
return;
}
fputs(str, fp); //写入字符串到文件
fclose(fp);
printf("\n字符串已经写入chartexfile.txt文件\n\n");
fp = fopen("charsumfile.txt", "w");
if (fp == NULL)
{
printf("创建文件charsumfile.txt错误\n");
system("pause");
system("cls");
return;
}
fprintf(fp, "%c%d", '@', Alph[0]); //将空格和大写字母的频度写入文件,
//@用来表示空格的字符,在哈夫曼编码中通常会用一个特殊的字符来表示空格,以便在编码和译码中能够准确处理空格字符
printf("字符 空格"); //打印字符频度信息
for (i = 1; i < 27; i++)
printf("%3c", i + 64);
printf("\n频度 %4d", Alph[0]);
for (i = 1; i < 27; i++)
{
fprintf(fp, "\n%c%d", i + 64, Alph[i]);
printf("%3d", Alph[i]); //在控制台输出各字符的出现次数
}
printf("\n\n各字符出现次数已经写入charsumfile.txt文件\n");
fclose(fp); //关闭文件指针,释放资源
}
fflush(stdin); //清空输入缓冲区
}
void Init()
{
int i, flag, com; //循环变量i,标志位flag,选择命令com
FILE *fp;
char ABC[27], temp; //ABC存储字符集,temp用于临时存储字符
int ABCnums[27]; //存储字符对应的频度
int filenums[27]; //用来存储每个字符出现频率的数组
system("cls");
printf("1.输入字符从charsumfile.txt中读取频度\n\n");
printf("2.输入字符和频度\n\n");
printf("输入一个数选择功能:");
flag = scanf("%d", &com);
printf("\n请输入字符集大小N, 1<= N <=27\n");
flag = scanf("%d", &N);
if (com == 1)
{
fp = fopen("charsumfile.txt", "r");
for (i = 0; i < 27; i++)
fscanf(fp, "%c%d\n", &temp, &filenums[i]); //读取文件字符和频度
fclose(fp);
printf("\n请输入N个不同的大写字母或空格,输入一个按一下回车\n");
for (i = 0; i < N; i++)
{
fflush(stdin);
ABC[i] = getc(stdin); //获取输入,getc只能用于标准输入流
while ((ABC[i] > 'Z' || ABC[i] < 'A') && ABC[i] != ' ') //检查输入是否正确
{
printf("输入的不是大写字母或空格,请重新输入\n");
fflush(stdin);
ABC[i] = getc(stdin);
}
if (ABC[i] == ' ')
ABCnums[i] = filenums[0];
else
ABCnums[i] = filenums[ABC[i] - 64]; //比如A在ASCII表中的值是65,为了将字符"A"映射到数组filenums的索引,需要减去64
} //这样'A'对应的ASCII值减去64就得到了filenums数组中对应的索引
}
else if (com == 2)
{
printf("\n输入大写字母或空格和频度例如\nA12\nC23\n\n");
for (i = 0; i < N; i++)
{
fflush(stdin);
ABC[i] = getc(stdin);
scanf("%d", &ABCnums[i]); //用户输入字符与频度的组合
}
}
printf("字符 ");
for (i = 0; i < N; i++)
{
if (ABC[i] == ' ')
printf("空格 ");
else
printf("%-4c ", ABC[i]);
}
printf("\n频度 ");
for (i = 0; i < N; i++)
printf("%-4d ", ABCnums[i]);
printf("\n"); //打印字符集和对应的频度
CreateHTree(ABC, ABCnums); //调用该函数创建哈夫曼树
PutHuffmanTree(); //打印哈夫曼树表格
fp = fopen("hfmTree.txt", "w");
if (fp == NULL)
{
printf("hfmTree.txt文件创建错误\n");
system("pause");
system("cls");
return;
}
fprintf(fp, "%-6s%-6s%-6s%-8s%-8s%-6s\n", "编号", "字符", "权值", "左孩子", "右孩子", "父亲"); //将哈夫曼树的信息输出到文件
for (i = 1; i <= N; i++)
{
if (HT[i].letter == ' ')
fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, '@', HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
else
fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
}
for (i; i <= 2 * N - 1; i++)
fprintf(fp, "%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
fclose(fp);
printf("\n哈夫曼树表已经写入hfmTree.txt中\n");
CreateHuffmanCode(); //调用该函数创建哈夫曼编码
Puteverycode(); //调用该函数,将每个字符的哈夫曼编码输出到控制台
system("pause");
system("cls");
fflush(stdin);
}
void CreateHTree(char a[], int w[])
{
struct HuffmanTree *pt; //定义指向struct HuffmanTree结构体的指针pt,用于遍历哈夫曼树数组
int m, i, s1, s2; //i为循环变量
m = 2 * N - 1; //表示哈夫曼树数组的大小
s1 = 1; //s1和s2用于记录选择的两个权值最小的节点
s2 = 2;
HT = (struct HuffmanTree *)malloc(sizeof(struct HuffmanTree) * (m + 1));
//使用malloc函数为哈夫曼树数组HT分配内存空间,大小为sizeof(struct HuffmanTree) * (m + 1)
pt = HT;
pt++; //初始化节点信息
for (i = 1; i <= N; i++, pt++, w++, a++)
{ //通过遍历循环,为前n各节点赋初值,权值来自频度数组w[],字符来自输入的字符数组a[]
pt->weight = *w;
pt->Lchild = 0;
pt->Rchild = 0;
pt->Parent = 0;
pt->letter = *a;
}
for (i; i <= m; i++, pt++)
{ //对于超过n的节点,将其权值、孩子、父亲等属性初始化为0
pt->weight = 0;
pt->Lchild = 0;
pt->Rchild = 0;
pt->Parent = 0;
pt->letter = 0;
}
for (i = N + 1; i <= m; i++)
{//从第n+1个节点开始,选择权值最小的两个节点,并将他们作为左右孩子构建新的节点,该新节点的权值为左右孩子权值之和
Select(i - 1, &s1, &s2);
HT[i].Lchild = s1;
HT[i].Rchild = s2;
HT[i].weight = HT[s1].weight + HT[s2].weight;
HT[s1].Parent = i;
HT[s2].Parent = i;
}
}
void Select(int n, int *s1, int *s2) //在构建哈夫曼树的过程中,这个函数会被调用多次,每次选择两个最小权值的节点进行合并
{
int i = 1, temp; //i作为循环变量,初始值为1,用于遍历哈夫曼树数组。temp用于在交换s1和s2时暂存其中一个的值
while (HT[i].Parent != 0 && i <= n) //寻找哈夫曼树数组中第一个parent属性为0的节点,即尚未构建父节点的节点
i++;
if (i == n + 1) //如果没有,函数直接返回
return;
*s1 = i++; //将当前找到的第一个没有父节点的节点的索引记录在s1中,并将i自增
while (HT[i].Parent != 0 && i <= n)
i++;
if (i == n + 1)
return;
*s2 = i++;
if (HT[*s1].weight > HT[*s2].weight)
{ //交换s1和s2,确保s1对应的节点权值小于等于s2对应的节点权值
temp = *s1;
*s1 = *s2;
*s2 = temp;
}
for (; i <= n; i++) //继续遍历剩余的节点,找到两个权值最小的节点
{
if (HT[i].Parent == 0) //确保它们没有父节点
{
if (HT[i].weight < HT[*s1].weight)
{
*s2 = *s1;
*s1 = i;
}
else if (HT[i].weight < HT[*s2].weight)
*s2 = i;
}
}
}
void PutHuffmanTree() //打印哈夫曼树的表格
{ //此函数不接受任何参数,它使用全局变量HT来访问哈夫曼树
int i, m = 2 * N - 1;
printf("\n输出哈夫曼树表\n");
printf("\n%-6s%-6s%-6s%-8s%-8s%-6s\n", "编号", "字符", "权值", "左孩子", "右孩子", "父亲");
for (i = 1; i <= N; i++) //遍历哈夫曼树的节点,并打印每个节点的信息
{
if (HT[i].letter == ' ')
printf("%-6d%-6s%-6d%-8d%-8d%-6d\n", i, "空格", HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
else
printf("%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
}
for (i; i <= m; i++)//打印父节点,继续打印剩余的节点信息,这些节点对应于哈夫曼树的内部(父)节点
printf("%-6d%-6c%-6d%-8d%-8d%-6d\n", i, HT[i].letter, HT[i].weight, HT[i].Lchild, HT[i].Rchild, HT[i].Parent);
printf("\n");
}
void CreateHuffmanCode() //为每个字符生成对应的哈夫曼编码
{
int start, i, p, f;
char col[28];
memset(HC, 0, sizeof(struct code) * 28); //用memset函数将全局变量HC数组初始化为0。HC是一个结构数组,用于存储字符及其对应的哈夫曼编码
for (i = 1; i <= N; i++) //for循环遍历哈夫曼树的每个叶子节点,这些叶子节点对应于输入的字符
{ //对于每个叶子节点,函数通过迭代向上遍历树来生成其哈夫曼编码
memset(col, 0, sizeof(col)); //用一个辅助数组col来存储编码,初始化为0
start = N - 1; //从当前节点开始
p = f = i; //向其父节点移动
while (HT[f].Parent != 0)
{
f = HT[f].Parent;
if (HT[f].Lchild == p) //根据是左孩子还是右孩子
col[--start] = '0'; //在col数组的开头插入对应的0或1
else if (HT[f].Rchild == p)
col[--start] = '1';
p = f;
}
HC[i].letter = HT[i].letter; //将当前字符存储在 HC[i].letter
strcpy(HC[i].col, col + start); //将col数组的内容复制到HC结构体数组中,即将生成的哈夫曼编码存储在HC[i].col
}
}
void Puteverycode() //将每个字符的哈夫曼编码输出到控制台
{
int i;
printf("%-6s%-8s\n", "字符", "编码");
for (i = 1; i <= N; i++)
{
if (HC[i].letter == ' ') //通过for循环遍历哈夫曼编码数组HC中的每个元素
printf("%-6s%-8s\n", "空格", HC[i].col);
else
printf("%-6c%-8s\n", HC[i].letter, HC[i].col);
}
}
int FILEhfmtree() //从名为"hfmTree.txt"的文件中读取哈夫曼树的信息并初始化哈夫曼树结构(HT)
{
FILE *fp; //文件打开
int i, temp;
char a;
fp = fopen("hfmTree.txt", "r"); //以只读模式"r"打开"hfmTree.txt"文件
if (fp == NULL)
return 0; //文件打开失败
getc(fp); //使用getc(fp)从文件中读取第一个字符
if (feof(fp)) //f开头表示文件操作,检查文件的结束标志,没有检查到就返回0,检查到了结束标志就返回非0.
return 0;
i = 1;
while (1)
{
fseek(fp, 42 * i + 6, SEEK_SET); //将指针fp指向文件开头
a = fgetc(fp); //使用fgetc(fp)从文件中读取一个字符
if (!a)
break;
i++;
}
N = i - 1; //根据计算得到的节点数N为哈夫曼树HT分配内存
HT = (struct HuffmanTree *)malloc(sizeof(struct HuffmanTree) * (2 * N));
for (i = 1; i <= 2 * N - 1; i++)
{
fseek(fp, 42 * i + 6, SEEK_SET);
a = fgetc(fp);
fscanf(fp, "%d%d%d%d", &HT[i].weight, &HT[i].Lchild, &HT[i].Rchild, &HT[i].Parent); //读取每个节点的信息
if (a == '@')
HT[i].letter = ' '; //将读取到的值分配给哈夫曼树结构的相应字段
else
HT[i].letter = a;
}
fclose(fp); //关闭文件
return 1; //返回1表示成功
}
void Codetxt() //对输入的文本进行哈夫曼编码,并将编码结果存储到文件"CodeFile.txt"中
{
char str[1024];
char codehf[2048];
int i, j;
FILE *fp;
system("cls");
if (NULL == HT) //检查哈夫曼树的存在
{
printf("\n当前内存中没有哈夫曼树,是否从hfmTree.txt文件读取(Y/N)\n");
fflush(stdin);
if ('Y' == getc(stdin))
{
if (FILEhfmtree() == 0)
{
printf("\nhfmTree.txt中没有哈夫曼树,请使用功能I初始化\n");
system("pause");
system("cls");
fflush(stdin);
return;
}
}
else
{
printf("\n没有构建哈夫曼树,无法进行编码\n");
system("pause");
system("cls");
fflush(stdin);
return;
}
}
CreateHuffmanCode(); //使用该函数为哈夫曼树中的每个字符生成相应的哈夫曼编码
if (NULL == (fp = fopen("ToBeTran.txt", "w")))
{
printf("\nToBeTran.txt文件创建错误\n");
system("pause");
system("cls");
return;
}
printf("\n请使用这些字符: ");
for (i = 1; i <= N; i++)
{
if (HC[i].letter == ' ')
printf("%-5s", "空格");
else
printf("%-5c", HC[i].letter);
}
printf("\n\n请输入一串要编码的文本\n");
fflush(stdin); //清除输入缓存
gets(str); //使用gets函数获取用户输入的一串文本
fputs(str, fp); //将用户输入的文本写入"ToBeTran.txt"文件
fclose(fp); //关闭文件
printf("\n输入的文本已经存入ToBeTran.txt文件\n");
memset(codehf, 0, sizeof(codehf));
for (i = 0; str[i]; i++)
{
for (j = 1; j <= N; j++)
if (str[i] == HC[j].letter) //遍历输入的文本,查找每个字符在哈夫曼编码表中对应的编码,将编码结果存储在codehf数组中
{
strcat(codehf, HC[j].col);
break;
}
if (j == N + 1)
{
printf("\n有不能编码的字符:%c\n退出后重试\n", str[i]);
system("pause");
system("cls");
return;
}
}
printf("\n编码结果:\n");
for (i = 0; codehf[i]; i++)
{
printf("%c", codehf[i]);
if (i % 50 == 49)
printf("\n"); //输出编码结果到控制台,以每行50个字符的方式输出编码结果
}
if (NULL == (fp = fopen("CodeFile.txt", "w"))) //打开"CodeFile.txt"文件失败
{
printf("\nCodeFile.txt文件创建错误\n");
system("pause");
system("cls");
return;
}
fputs(codehf, fp); //将编码结果写入"CodeFile.txt"文件
fclose(fp);
printf("\n编码结果已经存入CodeFile.txt文件\n");
system("pause");
system("cls"); //清空屏幕
fflush(stdin); //刷新输入缓冲区
}
void DeCodetxt() //对输入的哈夫曼编码进行译码,并将译码结果存储到文件"TextFile.txt"中
{
char str[1024];
char codehf[2048];
int i, j, start, temp;
FILE *fp;
system("cls");
if (NULL == HT)
{
printf("\n当前内存中没有哈夫曼树,是否从hfmTree.txt文件读取(Y/N)\n");
fflush(stdin);
if ('Y' == getc(stdin))
{
if (FILEhfmtree() == 0)
{
printf("\nhfmTree.txt中没有哈夫曼树,请使用功能I初始化\n");
system("pause");
system("cls");
fflush(stdin);
return;
}
}
else
{
printf("\n没有构建哈夫曼树,无法进行译码\n");
system("pause");
system("cls");
fflush(stdin);
return;
}
}
CreateHuffmanCode(); //使用该函数为哈夫曼树中的每个字符生成相应的哈夫曼编码
printf("\n当前的哈夫曼树中:\n");
Puteverycode(); //使用该函数在控制台上输出当前哈夫曼树的字符及对应的编码
if (NULL == (fp = fopen("CodeFile.txt", "r")))
{
printf("\nCodeFile.txt文件读取错误\n");
system("pause");
system("cls");
return;
}
fgets(codehf, 2048, fp); //从文件中读入字符串
fclose(fp);
printf("\nCodeFile.txt文件源码中: \n");
for (i = 0; codehf[i]; i++)
{
printf("%c", codehf[i]); //输出CodeFile.txt文件源码
if (i % 50 == 49)
printf("\n");
}
memset(str, 0, sizeof(str)); //使用memset函数清零str数组,用于存储译码结果
i = 0; //初始化i为0,用于遍历哈夫曼编码字符串
for (j = 0;; j++) //初始化j为0,用于遍历译码结果
{
start = 2 * N - 1;
for (i; codehf[i]; i++)
{ //使用循环遍历哈夫曼编码字符串,根据当前字符是"0"还是"1",在哈夫曼树中找到对应的子节点
if (codehf[i] == '0')
temp = HT[start].Lchild;
else if (codehf[i] == '1')
temp = HT[start].Rchild;
start = temp;
if (HT[start].Lchild == 0 && HT[start].Rchild == 0) //左右孩子都为0,则找到叶子节点
{
i++;
break;
}
}
str[j] = HT[start].letter; //将叶子节点对应的字符存入str数组中,并将i移动到下一个字符位置
if (codehf[i] == 0)
break;
}
printf("\n\n译码结果:\n");
for (i = 0; str[i]; i++)
{
printf("%c", str[i]);
if (i % 50 == 49) //以每行50个字符的方式输出译码结果
printf("\n");
}
fputs(str, fp); //将译码结果写入文件"TextFile.txt"
fclose(fp);
printf("\n\n译码结果已经存入TextFile.txt文件中\n");
system("pause");
system("cls"); //清空屏幕
fflush(stdin); //刷新输入缓存区
}