要求:统计文本文件字符频率,根据结果构建Huffman树,生成编码表和编码文件,最后根据编码表和编码文件还原文本文件。
我这里只讲实现思路和代码,Huffman结构原理略,默认课堂上已学。
目录
二、 统计字符串频率并构建Huffman树,输出Huffman编码表
一、Huffman树类的定义
1.结点定义:
这里与教材有所不同,有一个权重域weight和数据域data,用来存储字符
template <typename T, typename E>
struct HuffmanNode {
E weight; T data;//*********
HuffmanNode<T, E>* leftChild, * rightChild, * parent;
HuffmanNode() :leftChild(NULL), rightChild(NULL), parent(NULL) {}//构造函数
HuffmanNode(E elem, T item,/*权重和数据域(字母)*/ HuffmanNode<T, E>* pr,
HuffmanNode<T, E>* left, HuffmanNode<T, E>* right)
:weight(elem),data(item), parent(pr), leftChild(left), rightChild(right) {}//************
//重载操作符:
bool operator > (HuffmanNode<T, E> right) {return weight > right.weight;}
bool operator >= (HuffmanNode<T, E> right) {
return (weight > right.weight) || (weight == right.weight);
}
bool operator < (HuffmanNode<T, E> right) {return weight < right.weight;}
bool operator <= (HuffmanNode<T, E> right) {
return (weight < right.weight) || (weight == right.weight);
}
bool operator == (HuffmanNode<T, E> right) {return weight == right.weight;}
};
2.Huffman树类定义
template <typename T, typename E>
class HuffmanTree { //Huffman树类定义
public:
HuffmanTree(E w[],T d[], int n); //构造函数
~HuffmanTree() { deleteTree(root); } //析构函数
HuffmanNode<T, E>* getRoot() {return root;}
void output(HuffmanNode<T, E>* t);
protected:
HuffmanNode<T, E>* root;
//T Data[127]={' '};
//string Data;
int number=0;
void deleteTree(HuffmanNode<T, E>* t);
void mergeTree(HuffmanNode<T, E>* bt1,
HuffmanNode<T, E>* bt2, HuffmanNode<T, E>*& parent);
};
这里Huffman树是构造函数里实现的,用到的函数有deleteTree()和mergeTree()。
// 构造函数
template <typename T, typename E>
HuffmanTree<T,E>::HuffmanTree(E w[], T d[],int n):number(n)/*,Data(d)*/ {
//给出 n 个权值w[1]~w[n], 构造Huffman树
forHuff::MinHeap<E,HuffmanNode<T,E>> hp; //使用最小堆存放森林
HuffmanNode<T,E>* parent=NULL, *first, *second, temp;
HuffmanNode<T,E>* NodeList;
for (int i = 0; i < n; i++){
NodeList = new HuffmanNode<T, E>;
NodeList->weight = w[i];
NodeList->data = d[i];
NodeList->leftChild = NULL;
NodeList->rightChild = NULL;
NodeList->parent = NodeList;
hp.Insert(*NodeList);
}
for (int i = 0; i < n - 1; i++){ //n-1趟, 建Huffman树
hp.removeMinTop(temp); //根权值最小的树
first = temp.parent;
hp.removeMinTop(temp); //根权值次小的树
second = temp.parent;
mergeTree(first, second, parent); //合并
hp.Insert(*parent); //重新插入堆中
}
root = parent; //建立根结点
}
其他函数实现:
template <typename T, typename E>
void HuffmanTree<T, E>::deleteTree(HuffmanNode<T, E>* t) {//清除子二叉树,添加
if (t != NULL) {
deleteTree(t->leftChild);
deleteTree(t->rightChild);
delete t;
}
}
template <typename T, typename E>
void HuffmanTree<T, E>::mergeTree(HuffmanNode<T, E>* bt1,
HuffmanNode<T, E>* bt2, HuffmanNode<T, E>*& parent){
parent = new HuffmanNode<T, E>;
parent->leftChild = bt1;
parent->rightChild = bt2;
parent->parent = parent;//归并后的根结点的父指针指向新子树,在信息入堆后,出堆时可以找到对应子树
parent->weight = bt1->weight + bt2->weight; //权为左右子树数据之和
parent->data = 0;
bt1->parent = bt2->parent = parent; //处理两子树根结点的父指针
}
template<class T,class E>
void HuffmanTree<T, E>::output(HuffmanNode<T, E>* t) {
if (t != NULL) {
cout <<'('<< t->data << ':' << t->weight<<')';
if (t->leftChild != NULL || t->rightChild != NULL) {
cout << '{';
output(t->leftChild);
cout << ',';
if (t->rightChild != NULL)
output(t->rightChild);
cout << '}';
}
}
}
二、 统计字符串频率并构建Huffman树,输出Huffman编码表
这里我用Ascii码来存频率,一个字符出现一次,则num_fre对应位置则加1;
再用一个for循环,遍历这个数组,不为0则对应字符和频率放在name_ascii[j]=char(i)和fre_ascii[j]=num_fre[i]中,len++。
然后再构建Huffman树。
fstream file_in1;
fstream file_out1;
file_in1.open("D:\\HOME\\CUG\\2022下半年学习\\数据结构\\Huffman.txt", ios::in);
file_out1.open("D:\\HOME\\CUG\\2022下半年学习\\数据结构\\HuffCode.txt", ios::out);
if (!file_in1.is_open() || !file_out1.is_open()) {
cerr << "open Error!" << endl;
exit(1);
}
int num_fre[128] = { 0 };//统计所有ascii码字符出现的频率
char name_ascii[128] = { ' ' };//存放频次不为0的字符
int fre_ascii[128] = { 0 };//统计频次不为0的字符出现的频次
int len = 0;
Fun_statistics_char(file_in1, num_fre);
for (int i = 0; i < 128; i++) {
cout << num_fre[i] << ' ';
if ((i + 1) % 10 == 0)cout << endl;
if (num_fre[i] != 0) {
name_ascii[len] = char(i);//转为对应出现的字符
fre_ascii[len] = num_fre[i];//频率不为0的字符对应的频率
len++;
}
}
string singleCode;
HuffmanTree<char, int> huff(fre_ascii, name_ascii, len);
cout << endl;
HuffmanNode<char, int>* root = huff.getRoot();
huff.output(root);
Fun_find_huffcode(root, singleCode, file_out1);
file_in1.close();
file_out1.close();
system("pause");
cout << endl;
输出Huffman编码表我用的递归形式。
bool Fun_statistics_char(fstream& in, int fre[]) {
char ch;
while (in >> ch) {
if (int(ch) >= 0 && int(ch) <= 127) {
fre[int(ch)]++;
}
else {
cout << "出现未知字符!" << endl;
return false;
}
}
return true;
}
void Fun_find_huffcode(HuffmanNode<char, int>* current, string HuffCode, fstream& out) {
if (current->leftChild == NULL && current->rightChild == NULL) {
cout << current->data << ':' << current->weight << "**" << HuffCode << endl;
out << current->data << ':' << HuffCode << endl;
}
else {
Fun_find_huffcode(current->leftChild, HuffCode + "0", out);
Fun_find_huffcode(current->rightChild, HuffCode + "1", out);
}
}
输出编码表形式:
三、 根据Huffman编码表输出Huffman编码文件
根据上述编码表形式,我每次只读取一行,第一个字符放在Huffman_LetterList中,擦除掉前两个字符后,放在Huffman_CodeList中。
然后读取原来的文本文件,找到Huffman_LetterList[i]后就返回Huffman_CodeList[i]。
这里注意一下char和string的互换即可。
//读取文本文件和HuffCode文件,输出对应文本文件编码的filecode.txt文件
string* Huffman_CodeList = new string[len];
string* Huffman_LetterList = new string[len];//记得delete
fstream file_inCode, file_infile, file_outCode;
char list1[100];
InputBox(list1, 260, "Code File: ", "Input Saving Path.",
"D:\\HOME\\CUG\\2022下半年学习\\数据结构\\filecode.txt");
file_inCode.open("D:\\HOME\\CUG\\2022下半年学习\\数据结构\\HuffCode.txt", ios::in);
file_infile.open("D:\\HOME\\CUG\\2022下半年学习\\数据结构\\Huffman.txt", ios::in);
file_outCode.open(list1, ios::out);
if (!file_inCode.is_open() || !file_outCode.is_open()) {
cerr << "open Error!" << endl;
exit(1);
}
for (int i = 0; i < len; i++) {
getline(file_inCode, Huffman_CodeList[i]);//read
Huffman_LetterList[i] = Huffman_CodeList[i][0];
Huffman_CodeList[i].erase(0, 2);
cout << Huffman_LetterList[i] << ':' << Huffman_CodeList[i] << endl;
}
char singleLetter;
while (file_infile >> singleLetter) {//输出编码文件fileCode
string letter;
letter += singleLetter;
for (int i = 0; i < len; i++) {
if (letter == Huffman_LetterList[i]) {
file_outCode << Huffman_CodeList[i]; break;
}
}
}
MessageBox(GetHWnd(), "编码文件保存成功!", "温馨提示", MB_YESNOCANCEL);
file_infile.close();
file_inCode.close();
file_outCode.close();
system("pause");
输出编码文件:
四、 根据编码表和编码文件还原文本文件
这里编码有长有短,且文件编码之间没有空格,所以我就一次性读取整个编码文件,放在string str中,再新建一个string变量ss,每次循环ss+=str[i],直到ss == Huffman_CodeList[j],然后Huffman_LetterList[j]放入还原文件中,ss赋空开始下一次循环。
//将已输出的编码文件filecode重新读取,
//还原为原来的字符并放在另一个文件fileRestore.txt中
fstream file_restore, file_codefile;
char list2[100];
InputBox(list2, 260, "Code File: ", "Input Saving Path.",
"D:\\HOME\\CUG\\2022下半年学习\\数据结构\\fileRestore.txt");
file_codefile.open("D:\\HOME\\CUG\\2022下半年学习\\数据结构\\filecode.txt", ios::in);
file_restore.open(list2, ios::out);
if (!file_restore.is_open() || !file_codefile.is_open()) {
cerr << "open Error!" << endl;
exit(1);
}
string str, ss;
file_codefile >> str;
int lenCode = str.length();
int value = 0;
for (int i = 0; i < lenCode; i++) {
ss += str[i];
for (int j = 0; j < len; j++) {
if (ss == Huffman_CodeList[j]) {
file_restore << Huffman_LetterList[j];
ss = "";
break;
}
}
}
file_codefile.close();
file_restore.close();
MessageBox(GetHWnd(), "文本文件还原成功!", "温馨提示", MB_YESNOCANCEL);
cout << "Finished!" << endl;
初始文本文件和还原文件对比: