实验名称 | 哈夫曼编码实现文本压缩和解压缩 | ||||
实验内容 | 哈夫曼编码实现文本压缩 1、实现两个功能: 输入字符串,输出字符串的哈夫曼编码; 输入文本文件(英文),输出哈夫曼编码文件 2、附加:解压缩 | ||||
实验目的 | 1、理解并掌握哈夫曼树的构建及哈夫曼编码的实现 2、熟悉二叉树的结构特点及其应用 3、运用哈夫曼编码实现字符串和文件的编码转换 | ||||
实验步骤 |
进行选择,输入字符串或文本文件; 输入字符串直接将每个字符的权重和其编码以及字符串编码后的二进制数进行输出; 选择读取文本文件操作,则生成一个文件存放相关内容。 cout << "********************" << endl; cout << "1.字符串直接编码" << endl; cout << "2.读取文本编码" << endl; cout << "3.哈夫曼解压缩" << endl; cout << "0.退出" << endl; cout << "********************" << endl; cout << "请选择功能键:";
#define _CRT_SECURE_NO_DEPRECATE #include <iostream> #include <string.h> #include <stdlib.h> #include <fstream>
int n = 0; //哈夫曼编码表中的元素个数 char str1[100000]; //用于存放输入的字符串或者存放从文本文件中读取得到的字符串 int a[257]; //存放ascii编码表中所有字符出现的次数:1-256 对应于ASCII码的0-255 int b[257]; //存放出现过的字符的次数 char c[257]; //存放数组b中每个次数对应的字符 char str2[100000]; //存储从哈夫曼编码文件中读取到的哈夫曼编码 //哈夫曼树和哈夫曼编码的存储表示 typedef struct HTNode { int weight; //权重 int parent, lchild, rchild; }HTNode, * HuffmanTree; //动态分配数组存储哈夫曼树 typedef char** HuffmanCode; //动态分配数组存储哈夫曼编码表
· select函数 挑选n个结点中parent为0且权重最小的两个结点,并将其序号赋给s1和s2 void Select(HuffmanTree HT, int n, int& s1, int& s2) { for (int i = 1; i <= n; i++) { if (HT[i].parent == 0) { s1 = i; }//找到第一个父结点为0的点 } for (int i = 1; i <= n; i++){//遍历寻找权重最小且父结点为0的点 if (HT[i].parent == 0 && HT[i].weight < HT[s1].weight) { s1 = i; } //找到权重更小的点 } for (int i = 1; i <= n; i++){ if (HT[i].parent == 0 && i != s1) { s2 = i; break; } //找到第一个父结点为0的点,排除s1 } for (int i = 1; i <= n; i++) {//遍历寻找权重第二小且父结点为0的点 if (HT[i].parent == 0 && HT[i].weight < HT[s2].weight && i != s1) { s2 = i; } //找到权重更小的点,排除s1 } } 该函数用于寻找权重最小的两个结点,便于Create_HuffmanTree()函数中哈夫曼以结点权重建树。 ·Create_HuffmanTree()函数 构造含有n个字符的哈夫曼树,数组b中存储着各个字符出现的次数 void Create_HuffmanTree(HuffmanTree& HT, int b[], int n) { int s1 = 0, s2 = 0; HuffmanTree p; //用于遍历各结点 if (n <= 1) return; int m; //构造一棵哈夫曼树所需要的所有结点数 m = 2 * n - 1; HT = (HuffmanTree)malloc((m + 1) * sizeof(HTNode)); //0号结点不用 int i; for (i = 1, p = HT + 1; i <= n; i++, p++){//遍历第1到n个结点,把字符出现的次数赋值给哈夫曼树的weight p->weight=b[i]; p->parent=0; p->lchild=0; p->rchild=0; } for (i = n + 1; i <= m; i++, p++){ //后m-n个结点内容全部置0 p->weight=0; p->parent=0; p->lchild=0; p->rchild=0; } for (i = n + 1; i <= m; i++) //构造哈夫曼树 { Select(HT, i - 1, s1, s2); //从HT[1]到H[i-1]中挑选parent为0且权重最小的两个结点,其序号分别赋给s1,s2; HT[s1].parent = i; HT[s2].parent = i; //将结点i作为它们的父结点 HT[i].lchild = s1; HT[i].rchild = s2; //i的左右孩子结点分别为s1,s2 HT[i].weight = HT[s1].weight + HT[s2].weight; //父结点权重等于两个子结点之和 } } 首先通过p来遍历各个结点,遍历第1到n个结点,把字符出现的次数赋值给哈夫曼树的weight,接下来对结点进行合并,通过i-1次循环,每次合并两个结点。调用select函数,找到权值最小的结点s1和次小的结点s2,然后将他们合并到一个结点,结点i作为它们的父结点,i的左右孩子结点分别为s1,s2,父结点权重等于两个子结点之和。 · Create_HuffmanCode()函数 从叶子到根逆向求每个字符的哈夫曼编码 void Create_HuffmanCode(HuffmanTree& HT, HuffmanCode& HC, int n) { HC = (HuffmanCode)malloc((n + 1) * sizeof(char*)); //分配n个字符编码的头指针向量 char* cd = (char*)malloc(n * sizeof(char)); //分配求编码的工作空间 cd[n - 1] = '\0'; for (int 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)//左子树编码为0 cd[--start] = '0'; else cd[--start] = '1';//右子树编码为1 } HC[i] = (char*)malloc((n - start) * sizeof(char)); //为第i个字符编码开辟空间 strcpy(HC[i], &cd[start]);//从cd复制编码串到HC } free(cd); //释放求编码的工作空间 } 对树进行深度优先遍历,向左子树遍历,则编码加一个0,向右遍历则在编码中加入1。当找到叶子结点时,说明找到了字符,从cd复制编码串到HC。
//寻找字符s在对应的哈夫曼编码表中的数组编号 int Huffmancode_id(char s, char c[], int n) { int i; for (i = 1; i <= n; i++){ if (s == c[i]){ //找到返回编码在数组中的编号 return i; } } return 0; //找不到返回0 } //输入字符串,输出哈夫曼编码 void HuffmanCoding(HuffmanTree &HT, HuffmanCode &HC) { char x; int i = 1, j; int slength = 0; cout << "请输入字符串:"; cin >> x; //输入字符串,直到输入回车结束 while (x != '\n'){ str1[i] = x; x = getchar(); i++; slength++; } for (i = 1; i <= slength; i++){ //统计所有字符出现的次数 a[str1[i] + 1]++; } for (i = 1, j = 1; i <= 256; i++){ //删除未出现的字符,保留出现的字符 if (a[i] != 0) { n++; b[j] = a[i]; //将各字符出现的次数存入数组b中 c[j] = i - 1; //将字符存入数组c中 j++; } } Create_HuffmanTree(HT, b, n); //创建哈夫曼树和哈夫曼编码 Create_HuffmanCode(HT, HC, n); for (i = 1; i <= n; i++) //输出字符与哈夫曼编码的对应关系 cout << c[i] << ": " << HC[i] << endl; cout << "字符串对应的哈夫曼编码: "; for (i = 1; i <= slength; i++){ int k; k = Huffmancode_id(str1[i], c, n); //寻找字符对应的哈夫曼编码 cout << HC[k]; } cout << endl; } //输入英文字符文件,输出对应哈夫曼编码文件 void string_huffmanCoding_docu(HuffmanTree &HT, HuffmanCode &HC) { FILE* fpin; if ((fpin = fopen("/Users/songziang/Documents/数据结构与算法/数据结构与算法实验1/测试/测试/string_to_huffman.txt", "r")) == NULL) { printf("can't open the file!\n"); exit(0); } int i, j, slength; char x; i = 0; slength = 0; while (1) //读取文本文件中的字符串存放到数组s1中 { x = fgetc(fpin); //读一个字节 if (x == EOF) break; //到文件尾,退出循环。 i++; //读到的字符增加一个。 str1[i] = x; //将读到的字符赋给字符数组 } slength = i; //字符串的长度等于i; fclose(fpin); for (i = 1; i <= slength; i++) //统计所有字符出现的次数 { a[str1[i] + 1]++; } for (i = 1, j = 1; i <= 256; i++) //删除未出现的字符,保留出现的字符 { if (a[i] != 0) { n++; b[j] = a[i]; //将各字符出现的次数存入数组b中 c[j] = i - 1; //将字符存入数组c中 j++; } } Create_HuffmanTree(HT, b, n); //创建哈夫曼树和哈夫曼编码 Create_HuffmanCode(HT, HC, n); cout << "哈夫曼编码表如下:" << endl; //输出哈夫曼编码表 for (i = 1; i <= n; i++) cout << c[i] << ": " << HC[i] << endl; cout << "原始字符串为:"; for (i = 1; i <= slength; i++) cout << str1[i]; cout << endl; FILE* fpout; fpout = fopen("huffmancode.txt", "w+"); //清空内容 fclose(fpout); int k; fpout = fopen("huffmancode.txt", "a+"); //打开文件写入哈夫曼编码 cout << "字符串对应的哈夫曼编码为:"; for (i = 1; i <= slength; i++) { k = Huffmancode_id(str1[i], c, n); //找到对应的哈夫曼编码的序号 cout << HC[k]; fputs(HC[k], fpout); //将其哈夫曼编码写入文本文件 } cout << endl; fclose(fpout);
} //从哈夫曼编码表中寻找字符串,若找到返回字符串在编码表中的序号,找不到返回0 int seek_string(HuffmanCode HC, char s[]) { int i; for (i = 1; i <= n; i++){ if (strcmp(HC[i], s+1) == 0){ return i; } } return 0; } //对哈夫曼编码文件进行解压缩 void Huffmancode_unzip(HuffmanCode HC) { FILE* fpin, *fpout; fpin = fopen("huffmancode.txt", "r"); int i, j, slength, itemp; slength = 0; i = 0; while (1) //读取文本文件中的字符串存放到数组sh中 { char x = fgetc(fpin); //读一个字节 if (x == EOF) break; //到文件尾,退出循环。 i++; //读到的字符增加一个。 slength = i; //字符串的长度加1 str2[i] = x; //将读到的字符赋给字符数组 } fclose(fpin); fpout = fopen("huffman_to_string.txt", "w+"); //清空内容 fclose(fpout); fpout = fopen("huffman_to_string.txt", "a+"); cout << "解码后的字符串为:"; char temp[100]; //暂时存放一小段编码 memset(temp, 0, 100); //清空内容 for (i = 1, j = 1; i <= slength; i++){ temp[j] = str2[i]; j++; itemp = seek_string(HC, temp); //在哈夫曼编码表中寻找字符串编码,若找到,返回编码的编号 if (itemp){ fputc(c[itemp], fpout); cout << c[itemp]; memset(temp, 0, 100); //清空temp数组内容 j = 1; } } cout << endl; fclose(fpout); }
| ||||
实验步骤 | |||||
实验总结 | 通过本次哈夫曼编码实验,我切实体会到了哈夫曼树相关结构,最重要特点就是:其叶结点权值越小,离根越远,叶结点权值越大,离根越近。其中的关键就是合并,从二叉树集合中选取根节点权值最小的两棵二叉树分别作为左右子树构造一棵新的二叉树,这棵新二叉树的根节点的权值为其左、右子树根结点的权值和。通过书写相关代码,我对二叉树和哈夫曼树相关知识更加熟悉,同时也有了新的认识。同时在实验过程中,我也遇到了一些问题,比如从文件提取内容的相关语句的使用总是报错,经过搜索查找,我最终通过fstream库的相关知识解决了文件中字符的读取问题,学习的过程中温故是很重要的,温故可以使我们重新掌握忘却的知识,也可温故而知新。 |
代码如下:
#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <string>
#include <cstring>
using namespace std;
int NumOfChar;//字符种类
int numofchar;
int MaxSize; //静态链表实际使用大小 MAxsize-1也是哈夫曼树根结点的下标
int maxsize;
const int MaxLen = 257;//256+1
char *cd=new char[maxsize];
typedef struct Node{
int weight;//字符对应权重
char c;//字符类型
int parent,lchild,rchild;//双亲指针,左右孩子指针
}HTNode;
HTNode Hu[MaxLen*2];//哈夫曼树结点数为2*n-1
HTNode *hu;
typedef struct HuCode{
string code;
}HNode;
struct Lowest_Node//第0级节点的字符与频度
{
char ch;
int ch_num;
};
HNode HfCode[MaxLen];//用来存储哈夫曼编码
HNode hfcode[MaxLen];
int Caculate[MaxLen];//权重
int caculate[MaxLen];
void Get_CharInfo(void){//从文件中计算字符种类和权值
ifstream in;
in.open("Txt.txt");
char c;
while(in.get(c)){//不到达文件末尾的情况下读取字符
Caculate[c] ++;//将读到的字符对应权重+1
}
NumOfChar = 0;
for(int i = 0;i < MaxLen;i++)
if(Caculate[i]){//如果权重不为0
Hu[++NumOfChar].c = i;//存字符
Hu[NumOfChar].weight = Caculate[i]; //存权重
}
MaxSize = NumOfChar*2-1;//得到实际的哈夫曼树结点数量
in.close();//关闭文件
return;
}
void Creat_HuffmanTree(void){//建立哈夫曼树
int low1 = 0,low2 = 0;
int indexofParent = NumOfChar+1;//第一个双亲结点下标
while(1){//找到权重最低的两个结点
low1 = low2 = 0;
for(int i = 1;i < indexofParent;i++){//找low1
if(!low1 && !Hu[i].parent){//还没找到
low1 = i;
}else if(low1 && !Hu[i].parent && Hu[low1].weight > Hu[i].weight){
low1 = i;
}
}
for(int i = 1;i < indexofParent;i++){
if(i!=low1){
if(!low2 && !Hu[i].parent){//找low2
low2 = i;
}else if(low2 && !Hu[i].parent && Hu[low2].weight > Hu[i].weight){
low2 = i;
}
}
}
Hu[indexofParent].weight = Hu[low1].weight + Hu[low2].weight;//计算双亲结点的权重值
Hu[indexofParent].lchild = low1;
Hu[indexofParent].rchild = low2;
Hu[low1].parent = indexofParent;
Hu[low2].parent = indexofParent;//low2为0 ???? 时indexofParent = 8指针越界
if(indexofParent == MaxSize){//如果哈夫曼树已经构建完成就退出
break; //已经构建 MaxSize个结点
}
indexofParent++;//下个父节点位置
}
return;
}
void Create_Huffman_Code(int index,string scode){//通过哈夫曼树建立哈夫曼编码
//index 初始值为根结点
if(Hu[index].lchild){ //说明该结点不是叶子结点
scode.append("0");
Create_Huffman_Code(Hu[index].lchild,scode);
scode = scode.substr(0,scode.length()-1);//删除末尾元素 向右子树搜索
scode.append("1");
Create_Huffman_Code(Hu[index].rchild,scode);
}
else HfCode[(int)(Hu[index].c)].code = scode;//否则该字符完成编码
return;
}
void Print_HuffmanCodeToFile(void){//将 字符 权重 编码 输入到文件中
ofstream in;
in.open("HuffmanCode.txt");
for(int i = 1;i <= NumOfChar;i++){
in << "字符:" << Hu[i].c << " 权重:" << Hu[i].weight << " 编码:" <<HfCode[Hu[i].c].code<<endl;
}
in.close();
return;
}
void Txt_To_Huffmantxt(void){//文本转哈夫曼编码
ifstream in;
in.open("Txt.txt");
ofstream out;
out.open("TxtToHu.txt");
char c;
while(in.get(c)){
out << HfCode[(int)c].code;
}
in.close();
out.close();
return;
}
void HuffmantxtToTxt(void){//哈夫曼编码转文本
ifstream in;
in.open("TxtToHu.txt");
ofstream out;
out.open("HuToTxt.txt");
char c;
int index = MaxSize;
while(in.get(c)){
if(c == '1'){//1
if(Hu[Hu[index].rchild].rchild){//如果指向的结点不是叶子结点
index = Hu[index].rchild;//通过该结点是否有孩子来判断
}else{//是叶子结点
out << Hu[Hu[index].rchild].c;
index = MaxSize;//回到根结点
}
}else{//0
if(Hu[Hu[index].lchild].lchild){
index = Hu[index].lchild;
}else{
out << Hu[Hu[index].lchild].c;
index = MaxSize;
}
}
}
in.close();
out.close();
return;
}
int menu() {
int a;
cout << "请选择功能键:";
cin >> a;
if (a >= 0 && a <= 3) return a;
else return -1;
}
/*
void Creat_HuffmanTree2(void){//建立哈夫曼树
int low1 = 0,low2 = 0;
int indexofParent = numofchar+1;//第一个双亲结点下标
while(1){//找到权重最低的两个结点
low1 = low2 = 0;
for(int i = 1;i < indexofParent;i++){//找low1
if(!low1 && !hu[i].parent){//还没找到
low1 = i;
}else if(low1 && !hu[i].parent && hu[low1].weight > hu[i].weight){
low1 = i;
}
}
for(int i = 1;i < indexofParent;i++){
if(i!=low1){
if(!low2 && !hu[i].parent){//找low2
low2 = i;
}else if(low2 && !hu[i].parent && hu[low2].weight > hu[i].weight){
low2 = i;
}
}
}
hu[indexofParent].weight = hu[low1].weight + hu[low2].weight;//计算双亲结点的权重值
hu[indexofParent].lchild = low1;
hu[indexofParent].rchild = low2;
hu[low1].parent = indexofParent;
hu[low2].parent = indexofParent;//low2为0 ???? 时indexofParent = 8指针越界
if(indexofParent == maxsize){//如果哈夫曼树已经构建完成就退出
break; //已经构建 MaxSize个结点
}
indexofParent++;//下个父节点位置
}
return;
}*/
void Select(int k, int& idx1, int& idx2)
{ //在 HF[k](1 <= k <= i - 1)中选择两个双亲游标为 0 且权值最小的结点,
int min1, min2; //并返回他们在 HT 中的下标 idx1,idx2
min1 = min2 = 999999;
for (int i = 1; i < k; i++)
{
if (hu[i].parent == 0 && min1 > hu[i].weight)
{
if (min1 < min2)
{
min2 = min1;
idx2 = idx1;
}
min1 = hu[i].weight;
idx1 = i;
}
else if (hu[i].parent == 0 && min2 > hu[i].weight)
{
min2 = hu[i].weight;
idx2 = i;
}
}
}
void Creat_HuffmanTree2(void)
{
int m = 2 * maxsize - 1;
int idx1,idx2;
int i;
if(maxsize < 0) //树的结点数为 0,即空树
return;
/*二叉树初始化*/
hu = new HTNode[m + 1]; //0 号元素不使用,因此需要分配 m + 1 个单元,HT[m] 为根结点
for(i = 1; i <= m; i++)
{
hu[i].parent = hu[i].lchild = hu[i].rchild = 0; //初始化结点的游标均为 0
}
for(i = 1; i <= maxsize; i++)
cin >> hu[i].weight; //输入各个结点的权值
/*建哈夫曼树*/
for(i = maxsize + 1; i <= m; i++)
{
Select(i - 1,idx1,idx2);
/*在 HF[k](1 <= k <= i - 1)中选择两个双亲游标为 0 且权值最小的结点,
并返回他们在 HT 中的下标 idx1,idx2*/
hu[idx1].parent = hu[idx2].parent = i;
//得到新结点 i,idx1 和 idx2 从森林中删除,修改它们的双亲为 i
hu[i].lchild = idx1;
hu[i].rchild = idx2; //结点 i 的左右子结点为 idx1 和 idx2
hu[i].weight = hu[idx1].weight + hu[idx2].weight; //结点 i 的权值为左右子结点权值之和
}
}
void Create_Huffman_Code2(int n)
{//左0右1
//从叶子节点向上遍历,直到根节点,同时把字符存进cd数组里
cd[n-1]='\0';
for(int i=1;i<=n;i++)
{
int start=n-1;
int c=i;
int f=hu[i].parent;
while(f)
{
--start;
if(hu[f].lchild==c)
cd[start]='0';
else
cd[start]='1';
c=f;
f=hu[f].parent;
}
}
}
/*void Create_Huffman_Code2(int index,string scode){//通过哈夫曼树建立哈夫曼编码
//index 初始值为根结点
if(hu[index].lchild){ //说明该结点不是叶子结点
scode.append("0");
Create_Huffman_Code2(hu[index].lchild,scode);
scode = scode.substr(0,scode.length()-1);//删除末尾元素 向右子树搜索
scode.append("1");
Create_Huffman_Code2(hu[index].rchild,scode);
}
else hfcode[(int)(hu[index].c)].code = scode;//否则该字符完成编码
return;
}
*/
int main(){
int a;
string scode = "";//string 赋空
///char *p=(char *)s1.data();
cout << "********************" << endl;
cout << "1.字符串直接编码" << endl;
cout << "2.读取文本编码" << endl;
cout << "3.解压缩" << endl;
cout << "0.退出" << endl;
cout << "********************" << endl;
a=menu();
int length=0;//字符长度
string str;//目标 字符串
char *Z;
while (a != 0) {
switch (a) {
case 1:
cout<<"请输入第一个字符串:"<<endl;
cin>>str;
Z=(char *)malloc(str.size()*sizeof(char));
for(int i=0;i<str.size();i++){//不到达文件末尾的情况下读取字符
Caculate[str[i]] ++;//将读到的字符对应权重+1
}
numofchar = 0;
for(int i = 0;i < MaxLen;i++)
if(caculate[i]){//如果权重不为0
hu[++numofchar].c = i;//存字符
hu[numofchar].weight = caculate[i]; //存权重
}
maxsize = numofchar*2-1;//得到实际的哈夫曼树结点数量
Creat_HuffmanTree2();
Create_Huffman_Code2(maxsize);
cout<<cd<<endl;
a = menu();
break;
case 2:
cout << "相关文件已生成完成,请查看结果!\n";
//自动计算文本中所有字符的编码和权重 (字符在文件中出现次数) 保存在HuffmanCode.txt文件中
//能将文本按照编码加密并将得到的加密文本 TxtToHu.txt,并用哈夫曼树再翻译为文本 HuToTxt.txt
Get_CharInfo();//获取字符和权值
Creat_HuffmanTree();//建立哈夫曼树
Create_Huffman_Code(MaxSize,scode);//建立哈夫曼编码
Print_HuffmanCodeToFile(); //将设计的编码输出到文件中
Txt_To_Huffmantxt();//通过查找字符对应的编码 将文本转换为编码文件
HuffmantxtToTxt();//通过哈夫曼树 将编码文件转换为文本。
//system("pause");
a = menu();
break;
case 3:
cout << "解压缩完成!" << endl;
a = menu();
break;
default:
cout << "请输入正确的功能键!" << endl;
a = menu();
break;
}
}
return 0;
}