3001基于哈夫曼树的数据压缩算法(附思路及注释)

写在最前:我的思路用到了stl的map,对组pair相关知识。
描述

输入一串字符串,根据给定的字符串中字符出现的频率建立相应哈夫曼树,构造哈夫曼编码表,在此基础上可以对待压缩文件进行压缩(即编码),同时可以对压缩后的二进制编码文件进行解压(即译码)。

输入

多组数据,每组数据一行,为一个字符串(只考虑26个小写字母即可)。当输入字符串为“0”时,输入结束。

输出

每组数据输出2n+3行(n为输入串中字符类别的个数)。第一行为统计出来的字符出现频率(只输出存在的字符,格式为:字符:频度),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第二行至第2n行为哈夫曼树的存储结构的终态(形如教材139页表5.2(b),一行当中的数据用空格分隔)。第2n+1行为每个字符的哈夫曼编码(只输出存在的字符,格式为:字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+2行为编码后的字符串,第2n+3行为解码后的字符串(与输入的字符串相同)。

输入样例 1 

aaaaaaabbbbbccdddd
aabccc
0

输出样例 1

a:7 b:5 c:2 d:4
1 7 7 0 0
2 5 6 0 0
3 2 5 0 0
4 4 5 0 0
5 6 6 3 4
6 11 7 2 5
7 18 0 1 6
a:0 b:10 c:110 d:111
00000001010101010110110111111111111
aaaaaaabbbbbccdddd
a:2 b:1 c:3
1 2 4 0 0
2 1 4 0 0
3 3 5 0 0
4 3 5 2 1
5 6 0 3 4
a:11 b:10 c:0
111110000
aabccc

思路:

比较详细了。。希望你看过后可以先自己动手尝试实现~

哈夫曼树的节点说明一下:一个节点存的除了左右节点,parent,权值、还包括字符和它的编码。

看第一行输出,是每个字母及其出现频率,那么可以想到用map来存储相关信息,字母是key,出现次数通过遍历字符串累加作为value。

之后是创建树,先让题目里给的那几个节点初始化,双亲和子节点都是0,利用刚才的map的迭代器,存每个结点的data和weight。ass是辅助map,用来记录当前权值(key)和是否已经被使用过了(value,0表示用过了,1表示没有,一会创建新节点的权值要从这里面挑选value里是1且最小的返回key),故ass[it->second]=1.

FindParent返回的是一个嵌套的对组。设置最小权值min1,min2以及其对应的节点序号i,j,先遍历ass,依据上述选择标准选出min1。min2不能与min1相同——若不存在相同的权值,则min1!=min2;若存在相同的权值,那么min2不会被赋值,仍然是30000(一开始设个比较大的初始值),这种情况下再修改min2=min1。在哈夫曼树里遍历min1,min2,记录其对应的节点序号,此时又要考虑min1=min2的情况,若发生了,那么再单独遍历一次树,设一个flag记录第二次出现树里某个节点权值=min2的时候再赋值j=节点序号。返回一个包含两个最小权值和孩子序号的对组。

回到Create函数里,用上述返回值创建新节点,新权值是两个老权值相加,子节点序号也有了,顺便更新子节点的parent是现在节点的序号。最后别忘了令最后一次构造的节点parent=0!!

接下来是为每个节点创建哈夫曼编码,从每个叶子节点开始向上求parent,再看它是parent的左节点还是右节点,判断code是加一个0还是加一个1.注意:代码中code每次赋值不能写+=,不然顺序是反着的!!

接下来就到了轻松愉快的输出环节~第一行map m解决;表格和code就遍历树输出即可;解码如果想省事的话就直接输出原字符串xd,如果想写的话就在输出编码的时候用string decode记录下编码后的字符串,解码输出即可;

解码函数思路如下:在树里从头遍历编码字符串的每一位,是0就走左边,是1就走右边,直到到了叶子节点,输出那个节点的data即可,别忘了每次输出完了之后回到根节点呀!!

代码如下:

#include<iostream>
#include<string>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef struct HNode
{
	char data;//数据 
	int parent, weight, lchild, rchild;//双亲,权值,左右孩子 
	string code;//每个节点自己的哈夫曼代码 
}*HTree, HNode;
void DeCode(HTree ht, string s, int m)//解码 
{
	int i = m, j = 0;
	while (s[j])
	{
		if (s[j] == '0')
			i = ht[i].lchild;
		else
			i = ht[i].rchild;
		if (ht[i].lchild == 0 && ht[i].rchild == 0)//是叶子节点
		{
			cout << ht[i].data;
			i = m;//回到根节点
		}
		j++;
	}
}
void Cout(map<char, int> m, int size, HTree ht, string s)
{
	//打印字符和频率
	for (map<char, int>::iterator it = m.begin(); it != m.end(); it++)
	{
		if (it != m.begin())
			cout << " ";
		printf("%c:%d", it->first, it->second);
	}
	cout << endl;
	//打印表格
	for (int i = 1; i <= size * 2 - 1; i++)
		printf("%d %d %d %d %d\n", i, ht[i].weight, ht[i].parent, ht[i].lchild, ht[i].rchild);
	//打印字符及其对应的Huffman Code
	for (int i = 1; i <= size; i++)
	{
		if (i != 1)
			cout << " ";
		printf("%c:%s", ht[i].data, ht[i].code.c_str());
	}
	cout << endl;

	map<char, int>::iterator it = m.begin();
	string decode;
	for (int i = 0; i < s.length(); i++)//i控制输出次数
	{
		for (int k = 1; k <= size; k++, it++)//匹配ht里的data和当前字符
		{
			if (ht[k].data == s[i])//找到了
			{
				cout << ht[k].code;
				decode += ht[k].code;
				break;
			}
		}
		it = m.begin();
	}
	cout << endl;
	DeCode(ht, decode, size * 2 - 1);//解码
	cout << endl;
}
pair<pair<int, int>, pair<int, int>> FindParent(map<int, int>& ass, HTree ht, int size,int nowsize)//小蝌蚪找妈妈(大雾)
{
	int min1 = 30000, min2 = 30000;//记录当前的两个最小权值 
	int p1 = 0, p2 = 0;
	int i = 0, j = 0;//记录当前节点的两个孩子 
	for (map<int, int>::iterator it = ass.begin(); it != ass.end(); it++)
	{
		if (it->first < min1 && it->second == 1)
		{
			min1 = it->first;
			it->second = 0;
		}
	}
	for (map<int, int>::iterator it = ass.begin(); it != ass.end(); it++)
	{
		if (it->first < min2 && it->first != min1 && it->second == 1)
		{
			min2 = it->first;
			it->second = 0;
		}
	}
	if (min2 == 30000)//测试用例2
		min2 = min1;
	for (int k = 1; k < size; k++)
	{
		if (ht[k].weight == min1)
			i = k;
		if (ht[k].weight == min2)
			j = k;
		if (i != 0 && j != 0)
			break;
	}
	if (min1 == min2)//测试用例2
	{
		int flag = 0;
		for (int k = 1; k <= nowsize; k++)
		{
			if (min1 == ht[k].weight)
				flag++;
			if (flag == 2)
			{
				j = k; 
				break;
			}
		}
 	}
	pair<int, int > weight = make_pair(min1, min2);
	pair<int, int > parent = make_pair(i, j);
	pair< pair<int, int>, pair<int, int>>ans = make_pair(weight, parent);
	return ans;
}
void Create(HTree& ht, int size, map<char, int> mp, map<int, int>ass)//构造哈夫曼树
{
	if (size <= 1)
		return;
	else
	{
		int m = size * 2 - 1;//最多有这么多个节点
		int patch = m + 1;//如果不这么写,在下面的[]里写m+1的话,oj会报错compile error 
		ht = new HNode[patch];//0号下标不用,HT[m]表示根结点
		map<char, int>::iterator it = mp.begin();
		for (int i = 1; i <= size; i++, it++)//初始化前面几个有数值的已知节点
		{
			ht[i].lchild = 0;
			ht[i].rchild = 0;
			ht[i].parent = 0;
			ht[i].data = it->first;
			ht[i].weight = it->second;
			ass[it->second] = 1;
		}
		//补齐后面几个节点的weight,lch,rch
		for (int i = size + 1; i <= m; i++)
		{
			pair<pair<int, int>, pair<int, int>>ans = FindParent(ass, ht, m,i);
			ht[i].weight = ans.first.first + ans.first.second;
			ht[i].lchild = ans.second.first;
			ht[i].rchild = ans.second.second;
			ht[ans.second.first].parent = i;
			ht[ans.second.second].parent = i;
			ass[ht[i].weight] = 1;
		}
		ht[m].parent = 0;
	}
}
void SetCode(HTree ht, int size)
{
	for (int i = 1; i <= size; i++)
	{
		int fammem = ht[i].parent;
		while (fammem != 0)
		{
			if (ht[fammem].lchild == i)
				ht[i].code = '0' + ht[i].code;
			else
				ht[i].code = '1' + ht[i].code;
			fammem = ht[fammem].parent;
		}
	}
}
int cmp(char a, char b)
{return a < b;}
void SetMap(map<char, int>& m, string s)
{
	for (int i = 0; i < s.length(); i++)
		m[s[i]]++;
}
int main()
{
	char temp[100];
	while (cin >> temp && temp[0] != '0')
	{
		HTree ans;
		string s = temp;//为了获取长度
		string cd = temp;//为了记录下原式字符串
		sort(temp, temp + s.length(), cmp);//ASCII排序
		s = temp;//现在再次赋值,是排好序的了
		map<char, int>m;//记录字母种类以及出现次数
		map<int, int>ass;//辅助,为了让min2和min1不一样
		SetMap(m, s);	//存每个字符以及他们的频率到map中,记得参数写引用!!!
		Create(ans, m.size(), m, ass);//创建二叉树
		SetCode(ans, m.size());//为每个节点创建Huffman Code
		Cout(m, m.size(), ans, cd);//输出啦!
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值