换一种思路的霍夫曼编码(基于MFC)

本文介绍了使用MFC对话框应用程序实现霍夫曼编码的方法,包括从文本框或文件读取内容,统计字符频率,构建霍夫曼树,并给出字符的霍夫曼编码。通过可视化界面,用户可以选择输入方式,程序对数据进行处理,生成霍夫曼树并计算冗余量。同时,详细展示了代码逻辑,包括字符统计、字典构建、排序、霍夫曼树节点创建和连接等步骤。
摘要由CSDN通过智能技术生成


前言:
[首先]因为对网上搜索的代码我其实看得不是很有耐心(本人问题),所以用了自己的思路来写了霍夫曼编码,主要关键词: stl入栈出栈
[目的]对简单的字符数据进行霍夫曼编码,从文本框或本地文件地址中读入文本,输出每个字符的霍夫曼编码。

搭建简易的MFC对话框

图一 在vs studio中创建一个默认的MFC应用(记得选择对话框应用),通过视图打开工具箱,然后简单的拖拽对齐打造一个简单的界面。

可视化操作的逻辑设计

1.双击如图1所示中的‘运行’按钮(具体就是你自己设计的按钮),跳转到了cpp源文件的OnClickButton1()函数中。
2.接下来就是要读入文本框中的内容进行编码,先来说读入:

int state1 = 0,state2=0;
//定义两个状态,区分文本框文本读入和地址读入
void CHuffmanDlg::OnBnClickedButton1()
{
	CString content; //MFC应用中的所有可视化的输出和输入都是以CString存储的
	//这里定义一个content用来接收文本框读入的内容
	GetDlgItem(IDC_EDIT2)->GetWindowText(content);  
	//调用一个接收函数,参数IDC_EDIT2是你需要获取文本的文本框的ID,content是用来接收的变量
	std::string temp = CT2A(content.GetBuffer());
	//这里定义一个string类型,然后将CString类型转换为String,方便计算和操作

	if (state2 == 1) {//如果是地址读入
		std::ifstream infile;
		infile.open(temp,std::ios::in);
		if (!infile.is_open())
		{
			std::cout << "读取文件失败" << std::endl;
			return;
		}//通过ifstream中的open()函数打开本地文件
		char buf[1024];
		temp.clear();
		//创建一个字符数组用来存储输入流中读取到的字符
		//记得用完之后将读入的地址temp变量清空,下面要用来存储读入到的字符,省下一个变量空间
		while (infile.getline(buf, sizeof(buf)))
		{
			std::cout << buf << std::endl;
			temp = buf;
		}
		//将buf读到的字符存储到temp中
	}
}

还记得第一行定义的state1和state2吗,就是用来记录用户点击的是字符串还是本地文件地址(图一所示),接下来双击图一中的转态按钮创建两个函数:
在这里插入图片描述

void CHuffmanDlg::OnBnClickedRadio1()
{
	// TODO: 在此添加控件通知处理程序代码
	state1 = 1;
	state2 = 0;
}



void CHuffmanDlg::OnBnClickedRadio2()
{
	// TODO: 在此添加控件通知处理程序代码
	state2 = 1;
	state1 = 0;
}

对字符进行统计和排序

	std::map<char, float> dic;//创建字典<出现的字符,字符出现的次数>
	std::map<char, float> ::iterator it;//创建字典迭代器
	
	int flg = 0;//标志重复项
	
//对得到的字符串temp遍历
//对字典遍历
//如果字符在字典中第一次出现,标志为0且在字典中插入新的数据,反之标志为1,并且对应该字符的次数+1
	for (int i = 0; i <= temp.size(); i++) {
		for (it = dic.begin(); it != dic.end(); it++){
			if (temp[i] != (it->first)) {
				flg = 0;
			}
			else{
				flg = 1;
				it->second += 1;
				break;
			}
		}
		if (flg == 0) {
			dic.insert(std::pair<char, float>(temp[i], 1)); //插入新的种类
		}
	}


	char name = {  };
	dic.erase(name);
//这一步是为了删除创建字典时产生的第一个空的数据,不利于我们后面对字典的遍历操作

//将字符出现的次数转换为概率
	int sum = 0, count = 0;
	for (it = dic.begin(); it != dic.end(); it++) {
		count += 1;
		sum = it->second + sum;
	}
	for (it = dic.begin(); it != dic.end(); it++) {
		it->second = (it->second) / sum;
	}

//计算数据的冗余量
	std::vector<std::pair<char, float>> vec;//创建容器方便对数据进行计算
	float R = 0, H0 = log(count) / log(2), H = 0;//计算冗余量
	for (it = dic.begin(); it != dic.end(); it++) { 
		H = H + (it->second) * (log(it->second) / log(2));
		//vec中的first与second分别对应字典dic中的char类型和float类型中的数据
		vec.push_back(std::pair<char, float>(it->first, it->second));
	}
	R = H0 + H;
//R就是我们要计算的冗余量

//最后我们要将数据由小到大进行排序,方便我们后面对数据创建霍夫曼树
sort(vec.begin(), vec.end(),cmp);
//容器的好处就在这里体现出来了

这里有一个小小的细节需要注意,sort()函数默认调用的cmp函数是由大到小排序的,所以我们要在源文件中找到cmp()函数进行修改:

bool cmp(std::pair<char, float> a, std::pair<char, float> b) {
	return a.second < b.second;//这里的<或者>就用来控制降序还是升序排序的
}

霍夫曼树

现在我们在跳转到xxx.h的头文件中(xxx为你的工程名字),定义出我们的霍夫曼树和对应的函数:

struct HuffmanNode {
	char chara; //字符
	float weight; //概率,也就是权值
	HuffmanNode* lchild, * rchild; //对应的左子树和右子树
};

关于霍夫曼树的基本原理这里就不赘述了,很多文章都有介绍,这里我只做结构和操作的解释:
在这里插入图片描述
接下来我们写一下有关霍夫曼树的一系列操作的函数:
1.创建节点

static HuffmanNode* creatnode(char fchara, float fweight) {
	HuffmanNode* node = (HuffmanNode*)malloc(sizeof(HuffmanNode));
	//分配一个节点空间
	node->chara = fchara;
	node->weight = fweight;
	node->lchild = NULL;
	node->rchild = NULL;
	return node;
}

2.将两个节点连接成一个新的节点(由两个子节点合成一个父节点)

static HuffmanNode* linknode(HuffmanNode* x1, HuffmanNode* x2) {
	HuffmanNode* node = (HuffmanNode*)malloc(sizeof(HuffmanNode));
	node->chara = 'l'; //将合成之后的节点字符名命名为l方便后面的操作
	node->weight = x1->weight + x2->weight;
	node->lchild = x1;
	node->rchild = x2;
	return node;
}

这里用一个图解释一下:
在这里插入图片描述
3.比较三个节点两两组合的权值总和大小

static bool cmpnode(HuffmanNode* x1, HuffmanNode* x2, HuffmanNode* x3) {
	float sum1 = x1->weight + x2->weight;
	float sum2 = x2->weight + x3->weight;
	if (sum1 <= sum2) {
		return 1;
	}
	else return 0;
}

这个函数我们稍后用到的时候再详细讲一下,先把它写到我们的头文件中

4.输出霍夫曼树的所有结点(不包括合成节点)

static void show(HuffmanNode* root) {
	if (root) {
		if (root->chara == 'l') {
			show(root->lchild);
			show(root->rchild);
		}
		else {
			std::cout << root->chara<<std::endl;
		}
	}
}

5.查找某个字符的霍夫曼编码(最关键的函数)

static int flag = 0;//设置一个状态用来判断是否查找到我们需要编码的字符
static std::vector<char> search(HuffmanNode* root,char target,std::vector<char> code) {
	if (root) {
		if (root->chara == target) {
			flag = 1;
			return code;
		}
		else {
			if(flag==0)code.push_back('0');
			if (flag == 0)code=search(root->lchild, target, code);
			if (flag == 0)code.push_back('1');
			if (flag == 0)code=search(root->rchild, target, code);
			if (flag == 0)code.pop_back();
		}
	}
	else { 
		code.pop_back();
		return code; 
	}
	return code;

基本思路:在查找的过程中,进入左子树我们在code容器中压入0,进入右子树我们压入1,若没有找到且返回上层函数则将我们进入左右子树入栈的0和1都pop掉。这里我们后面举个例子讲解会比较清晰。

6.重置状态

static void change_flag() {
	flag = 0;
}

举个例子

这里我引用了一组数据:
BABACACADDAABBCBBAEBEDDABBBEEE

对应我们产生的字典:
B:10/30
A:8/30
C:3/30
D:4/30
E:5/30

转换为容器进行排序:
B:10/30
A:8/30
E:5/30
D:4/30
C:3/30

对应排序后的容器我们创建出一个霍夫曼树:
在这里插入图片描述
我们用flg来记录树是否满足有两个节点的情况。
在这里插入图片描述
在这里插入图片描述
第一轮循环结束。
在这里插入图片描述
在这里插入图片描述
到这里循环结束,我们的霍夫曼树也创建好了,所有的节点都保存在root节点中。
源码如下:

int flag=0;
HuffmanNode*root=(HuffmanNode*)malloc(sizeof(HuffmanNode));
HuffmanNode*root2 = (HuffmanNode*)malloc(sizeof(HuffmanNode));

if (vec.size() > 2) {
		for (std::vector<std::pair<char, float>>::iterator ii = vec.begin(); ii != vec.end();) {
		//如果是第一次创建
			if (flag == 0) {
				HuffmanNode* n1 = creatnode(ii->first, ii->second);
				ii++;
				if (ii != vec.end()) {
					HuffmanNode* n2 = creatnode(ii->first, ii->second);
					HuffmanNode* link = linknode(n1, n2);
					root = link;
					ii++;
					flag = 1;
				}
			}

		//满足树中有两个节点后
			if (flag == 1) {
				HuffmanNode* n1 = creatnode(ii->first, ii->second);
				ii++;
				if (ii != vec.end()) {
					HuffmanNode* n2 = creatnode(ii->first, ii->second);
					if (cmpnode(root, n1, n2)) {
						if (root->weight > n1->weight) {
							root = linknode(n1, root);
						}
						else { root = linknode(root, n1); }
					}
					else {
						HuffmanNode* link = linknode(n1, n2);
						if (root->weight > link->weight) {
							root = linknode(link, root);
						}
						else { root = linknode(root, link); }
						ii++;
					}
				}
				else {
					root = linknode(root, n1);
				}
			}
		}
	}

如果需要创建的霍夫曼树节点数小于等于2,我们就简单地分配编码就好了:

if (vec.size() <= 2)
	{
		CString result;
		std::string coding,s,code;
		std::vector<std::pair<char, float>>::iterator ic=vec.begin();

		s = "的霍夫曼编码为:";
		s = ic->first + s;
		code = '0';
		coding = s + code;
		result = coding.c_str();
		AfxMessageBox(result);

		if (vec.size() == 2) {
			coding.clear();
			ic++;
			s = "的霍夫曼编码为:";
			s = ic->first + s;
			code = '1';
			coding = s + code;
			result = coding.c_str();
			AfxMessageBox(result);
		}
	}

最后,我们只要对创建好的霍夫曼树进行编码就可以了:
在这里插入图片描述
这里的第三步第四步和第二步是一样的所以函数图也是一样的。
在这里插入图片描述
接下来就是最重要的两个环节了:
在这里插入图片描述
函数图中右下角的罗马数字代表第几次的函数,有利于大家理解递归函数的过程。

在这里插入图片描述
后面的过程都是一样的就不赘述了,最后我们就得到了某一个字符的霍夫曼编码:
在这里插入图片描述
对霍夫曼树中的所有字符编码 只需要循环遍历调用我们前面写好的search函数就可以了:

std::string coding;

	for (std::vector<std::pair<char, float>>::iterator ii = vec.begin(); ii != vec.end();ii++) 
	{
		pointer = root;
		code = search(pointer, ii->first, code);//对每一个字符的编码函数
		std::string::iterator is = coding.begin();
		std::string s = "的霍夫曼编码为:";
		std::cout << ii->first << "的霍夫曼编码为:";
		
		coding.insert(is,ii->first);
		coding=coding+s;
		is = coding.end();

		for (std::vector<char>::iterator it = code.begin(); it != code.end(); it++) {
			is=coding.insert(is, *it);
			std::cout << *it;
		}
		CString result;
		result = coding.c_str();
		AfxMessageBox(result);
		coding.clear();
		std::cout << std::endl;
		code.clear();
		change_flag();
	}	

小结

内容其实有点多了,本文章只用于学习交流,写的不好的地方欢迎指正批评!完整程序会在后面更新放出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值