数据结构:Huffman树及其编码实现(C++)

该博客详细介绍了如何实现Huffman编码,包括构建Huffman树、生成编码表、输出编码文件以及根据编码还原文本文件的完整过程。通过统计文本文件中字符的频率,构建最优二叉树,进而压缩文件,并能成功解压回原始文本。
摘要由CSDN通过智能技术生成

要求:统计文本文件字符频率,根据结果构建Huffman树,生成编码表和编码文件,最后根据编码表和编码文件还原文本文件。

我这里只讲实现思路和代码,Huffman结构原理略,默认课堂上已学。

目录

一、Huffman树类的定义

1.结点定义:

2.Huffman树类定义

二、 统计字符串频率并构建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;

初始文本文件和还原文件对比:

 

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值