基于哈夫曼树的英文文本数据压缩算法

基于哈夫曼树的英文文本数据压缩算法

(1)问题描述

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

(2)输入要求

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

(3)输出要求

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

字符:编码),每两组字符之间用一个空格分隔,字符按照ASCII码从小到大的顺序排列。第2n+2行为编码后的字符串,第2n+3行为解码后的字符串(与输入的字符串相同)。

(4)输入样式

aaaaaaabbbbbccdddd (案例1)

aabccc            (案例2)

0

(5)输出样例

a:7  b:5  c:2  d:4   (案例1)

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:l   c:3      (案例2)
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

代码如下(包含注释解释部分代码):

#include<iostream>
#include<cstring>
#include<string>

using namespace std;

struct Num
{
	char ch; //字符
	int num; //字符对应个数
}an[7000], temp;

//sort1根据字符个数从大到小进行排序
void sort1(Num* bn)
{
	Num te; //相当于一个临时变量,用作保存数据来完成数据的交换
	int i, j;
	//冒泡排序:
	for (j = 0; j < 26; j++)  //总共二十六个字母,则大循环进行二十六次(0-25共二十六次)
		for (i = 0; i < 26-j-1; i++) 
		{
			if (bn[i].num < bn[i + 1].num)
			{
				te = bn[i];
				bn[i] = bn[i + 1];
				bn[i + 1] = te;
			}
		}
}

//sort2根据字符的ASCII码值从小到大进行排序
void sort2(Num* bn, int n) //n为统计的所输入数据中的字符种类数
{
	Num te;
	int i, j;
	//冒泡排序:
	for (j = 0; j < n; j++)
		for (i = 0; i < n-j-1; i++)
		{
			if (bn[i].ch > bn[i + 1].ch && bn[i + 1].num > 0) //交换条件为当前字符的权重大于下一个字符的权重,且下一个字符的数量大于0,即下一个字符存在
			{
				te = bn[i];
				bn[i] = bn[i + 1];
				bn[i + 1] = te;
			}
		}
}

//哈夫曼数的结构体
typedef struct
{
	int weight; //权重
	int parent, lchild, rchild; //父节点,左右孩子节点
	int vis; //作为一个标识符使用(防止重复进行select过程)
	char ch; //节点对应的字符
	char strc[60]; //哈夫曼编码
	int len;
	int num; //某个字符在哈夫曼树中的个数
}HTNode, * HuffmanTree; //HTNode == * HuffmanTree(解引用)


//Select根据权重来安排哈夫曼树中节点的位置
//第一个参数为指向哈夫曼树的指针的引用,第二个参数为所处哈夫曼树中节点的个数,第三第四个参数为两个标识符,且参数为引用传递,可以通过函数改变该标识符的值
void Select(HuffmanTree& HT, int k, int& s1, int& s2) 
{
	int i;
	HTNode h1, h2; //HTNode对应的应该是指向哈夫曼树的指针
	int t1, t2; //作为标记,分别标记h1,h2对应节点所处的位置
	t1 = t2 = 0; 
	h1 = HT[1];
	h1.weight = 999999;
	for (i = 1; i <= k; i++) //循环共k个节点,分别与现有的h1.weight比较权重,从而选出所含权重最小的节点值并将该值赋值给h1.weight
	{
		if (HT[i].weight <= h1.weight && HT[i].vis == 0)
		{
			h1 = HT[i];
			t1 = i;
		}
	}
	h2 = HT[2];
	h2.weight = 999999;
	for (i = 1; i <= k; i++) //再对h2进行和h1相同的选择赋值过程
	{
		if (HT[i].weight <= h2.weight && t1 != i && HT[i].vis == 0)
		{
			h2 = HT[i];
			t2 = i;
		}
	}
	//在h1和h2权重相同的情况下,将所含字符数更少的字符放在前面
	if (h1.weight == h2.weight && HT[t1].num > HT[t2].num)
	{
		int temp;
		temp = t1;
		t1 = t2;
		t2 = temp;
	}

	//将更新后的t1,t2分别赋值给s1,s2
	s1 = t1;
	s2 = t2;
	//令vis=1,标识HT[s1]和HT[s2]这两个节点以及经过了Select作用,防止重复进行该过程
	HT[s1].vis = 1;
	HT[s2].vis = 1;
	return;
}

//哈夫曼树的编码函数
//第一个参数是指向哈夫曼树指针的引用,第二个参数是字符串的长度,第三个参数为字符串,第四个参数是哈夫曼树中的第n个节点,第五个参数为编码(左孩子为'0',右孩子为'1')
void code(HuffmanTree& HT, int len, const char str[], int n, char cc)
{
	int i;
	//HT[n].strc的前len-2个字符都设置为str[i],第len-1个字符设置为编码值cc,第len个字符设置为空'\0'
	for (i = 0; i < len - 1; i++)
		HT[n].strc[i] = str[i];
	HT[n].strc[i] = cc;
	HT[n].strc[i + 1] = '\0';
	//利用递归完成整棵树的编码
	//如果左子树不为空,则一直编码直到左子树为空
	if (HT[n].lchild != 0)
		code(HT, len + 1, HT[n].strc, HT[n].lchild, '0');
	//如果右子树不为空,则一直编码直到右子树为空
	if (HT[n].rchild != 0)
		code(HT, len + 1, HT[n].strc, HT[n].rchild, '1');
}

//哈夫曼树的构造(利用了Select函数进行选择排序以及Code函数进行编码)
//第一个参数是一个指向哈夫曼树的指针的引用,第二个参数为n个子叶节点(即没有左右孩子的节点),第三个参数为
void CreateHuffmanTree(HuffmanTree& HT, int n, char* str, int n1)
{
	int s1, s2;
	s1 = s2 = 0;
	if (n <= 1) //如果子叶节点小于一,则直接返回,完成哈夫曼树的构造
		return;
	int m = 2 * n - 1; //n个叶子节点的哈夫曼树有2n-1个节点
	//通过new语法创建哈夫曼树节点
	HT = new HTNode[m + 1];

	//初始化哈夫曼树中的所有节点
	for (int i = 1; i <= m; i++)
	{
		HT[i].parent = 0; 
		HT[i].lchild = 0; 
		HT[i].rchild = 0;
		HT[i].vis = 0; 
		HT[i].len = 0; 
		HT[i].num = i;
		HT[i].weight = 0;
	}
	for (int i = 1; i <= n; i++)
	{
		//an[]是创建的结构体数组
		//将结构体数组中的数据依次赋值给哈夫曼树中的节点
		HT[i].weight = an[i - 1].num;
		HT[i].ch = an[i - 1].ch;
	}
	//总共m个节点,其中有n个叶子节点,则循环从n+1开始,到m截至,对n+1 -- m 中的节点进行排序
	for (int i = n + 1; i <= m; i++)
	{
		Select(HT, i - 1, s1, s2);
		//同一个父节点推出两个左右子节点HT[s1],HT[s2]
		HT[s1].parent = i;
		HT[s2].parent = i;
		//父节点的左孩子为s1
		HT[i].lchild = s1;
		//父节点的右孩子为s2
		HT[i].rchild = s2;
		//父节点的权重为左孩子的权重加上右孩子的权重
		HT[i].weight = HT[s1].weight + HT[s2].weight;
	}
	int len = HT[m].len;
	HT[m].strc[len] = '1';
	//根据 左--0,右--1 来进行哈夫曼树的编码 
	if (HT[m].lchild)
		code(HT, 1, "", HT[m].lchild, '0');
	if (HT[m].rchild)
		code(HT, 1, "", HT[m].rchild, '1');
	return;
}

int main()
{
	HuffmanTree h;
	int n, n1;
	char str[700], str2[700];
	cout << "请输入字符串(输入0时结束算法)" << endl;
	while (cin >> str) //为字符串str完成输入,当输入'0'时结束while循环
	{
		if (str[0] == '0' && str[1] == '\0')
			break;
		n1 = 0;
		for (int i = 0; i < 27; i++)
		{
			an[i].num = 0; //初始化所有字母的个数为0
			//将an[0-25]分别填放a~z这二十六个字母
			an[i].ch = 'a' + i;
		}
		n = strlen(str); //n为字符串str的长度
		for (int i = 0; i < n; i++)
		{
			//根据输出的字符串str来获取其中每个字母所含的个数 
			//eg.如果某个str[i] == b ,则str[i]-'a' ==1 -- 根据ASCII码计算,即为an[1].num加一同时an[1]对应的字母字符也是b,相当于记录了str字符串中所含字母b的个数加一
			an[str[i] - 'a'].num++; 
			str2[i] = str[i];
			str2[i + 1] = '\0';
		}
		//根据字符个数从大到小进行排序
		sort1(an);
		for (int i = 0; i < 26; i++)
		{
			if (an[i].num != 0)
				n1++;//n1统计an中所含的字符数(如果an[i].num == 0 则说明an[i]对应的字符字母不存在)
		}
		//根据字符的ASCII码值从小到大进行排序
		sort2(an, n1);

		//创建哈夫曼数(CreateHuffmanTree中包含Select和Code函数)
		CreateHuffmanTree(h, n1, str, n1);
		//如果字符串str中只有一个字符
		if (n1 == 1)
		{
			h = new HTNode[3];
			h[1].weight = n;
			h[1].parent = h[1].lchild = h[1].rchild = 0;
			h[1].ch = str[0];
			h[1].strc[0] = '0';
			h[1].strc[1] = '\0';
		}


		//输出:
		cout << "输出为: " << endl;

		//输出每个字符在字符串中出现了几次
		int f = 0;
		for (int i = 0; i < 26; i++)
			if (an[i].num > 0)
			{
				f++;
				cout << an[i].ch << ":" << an[i].num;
				if (f == n1)
					cout << endl;
				else
					cout << " ";
			}


		//输出哈夫曼数的储存结构的终态
		for (int i = 1; i <= 2 * n1 - 1; i++)
		{
			cout << i << " " << h[i].weight << " " << h[i].parent << " " << h[i].lchild << " " << h[i].rchild << endl;
		}

		//输出每个字符的哈夫曼编码
		for (int i = 1; i < n1; i++)
			cout << h[i].ch << ":" << h[i].strc << " ";
		cout << h[n1].ch << ":" << h[n1].strc;
		cout << endl;

		//输出解码后的字符串
		for (int i = 0; i < n; i++)
		{
			for (int j = 1; j <= n1; j++)
				if (h[j].ch == str[i])
					cout << h[j].strc;
		}
		cout << endl;
		cout << str << endl;

		//重置哈夫曼数中的数据
		for (int i = 1; i <= 2 * n1 - 1; i++)
		{
			h[i].parent = 0;
			h[i].lchild = 0; 
			h[i].rchild = 0;
			h[i].vis = 0;
			h[i].len = 0;
			h[i].num = i;
		}
		cout << "请继续输入字符串(输入0结束算法)" << endl;
	}

	return 0;
}

程序运行结果:

  • 10
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值