基于c++的哈夫曼编码

目录

前序

创建哈夫曼树

存了什么数据和数据权值

存储数据的类

如何读取全部输入的数据

注意

创建哈夫曼树

首先把数组存到链表里

​直接创建树 

编码

解码

源码


前序

我们都知道哈夫曼编码可以有效压缩数据空间,一般可以节省20%~90%左右,所以哈夫曼树如何创建就成了巨大的问题 ●)o(● 。在这里我说一下我的建树过程仅供大家参考。(所以在这里我就不讲解关于哈夫曼树的一些概念了,看之前可以搜搜 (ง •̀_•́)ง 加油 )

创建哈夫曼树

存了什么数据和数据权值

我们知道哈夫曼树是变长编码,单个编码长度在不同树中不一致,而像ASCII单个编码长度就是固定的。所以我们得首先得出你要存什么东西和它的权值(也就是出现次数),这个东西你可以通过读取文件啥的来实现,在这里为了方便演示,我通过输入数据来让树知道存什么数据和每个数据出现的次数。用数组来存储它备用。

存储数据的类

注:出现的次数就是你输入时输进去这个数据的次数。

如何读取全部输入的数据

虽然我们知道创建树之前要先存进去数据,但是如何读取数据呢?

首先我想到的就是 string 但是如果出现空格呢?空格会直接把 string 截断,导致无法全部读取,所以在这里我们就要用到 getchar( ) 这样这个问题就很容易解决了 \\\\٩( 'ω' )و ////

代码如下:

void Tree::enter_letter()//输入字母
{
	cout << "输入文段:";
	char letter = getchar();
	while (1)
	{
		if (letter == '\n')//跳出
		{
			break;
		}
		if (letter >= 97 && letter <= 122)//小写字母
		{
			sav_letter[letter - 97]->set_quantity(sav_letter[letter - 97]->get_quantity() + 1);
		}
		if (letter >= 65 && letter <= 90)//大写字母转为小写
		{
			sav_letter[letter + 32 - 97]->set_quantity(sav_letter[letter + 32 - 97]->get_quantity() + 1);
		}
		if (letter == 33)//!
		{
			sav_letter[26]->set_quantity(sav_letter[26]->get_quantity() + 1);
		}
		if (letter == 44)//,
		{
			sav_letter[27]->set_quantity(sav_letter[27]->get_quantity() + 1);
		}
		if (letter == 32)//空格
		{
			sav_letter[28]->set_quantity(sav_letter[28]->get_quantity() + 1);
		}
		if (letter == 46)//.
		{
			sav_letter[29]->set_quantity(sav_letter[29]->get_quantity() + 1);
		}
		if (letter == 63)//?
		{
			sav_letter[30]->set_quantity(sav_letter[30]->get_quantity() + 1);
		}
		letter = getchar();
	}
	system("cls");
	//给字母数组排序
	int new_qulity;//临时数量排序
	char new_letter;//临时字符排序
	for (int i = 0; i < 30; i++)          //冒泡排序,从小到大;31个字母,排30次即可
	{
		for (int k = 0; k < 30 - i; k++)
		{
			if (sav_letter[k]->get_quantity() > sav_letter[k + 1]->get_quantity())
			{
				new_qulity = sav_letter[k]->get_quantity();
				new_letter = sav_letter[k]->get_letter();
				sav_letter[k]->set_quantity(sav_letter[k + 1]->get_quantity());
				sav_letter[k]->set_letter(sav_letter[k + 1]->get_letter());
				sav_letter[k + 1]->set_quantity(new_qulity);
				sav_letter[k + 1]->set_letter(new_letter);
			}
		}
	}
	return;
}

注意

大家可以看出这个代码有点多,主要原因是我主要存的是24个字母和一些常用的符号,在这里边没有用到的数据我把权值赋值为0,这就有点违背变长编码,所以在存储时我们把用到的存起来,没有用到的可以不存到数组里,可是怎末实现就靠你们更改了!!! (♡ ὅ ◡ ὅ )ʃ♡                            提示一下用ASCII。

创建哈夫曼树

我们知道,对于交换位置来说链表还是比数组比较实现的,指针指向发生改变或者里边数据交换即可,我们先定义链表类用于存先前信息和建树。

首先把数组存到链表里

 我们把排好序的数组先存到链表里。

直接创建树 

创建树就是拿两个最小的当作叶子,给他一个根节点,然后把权值赋给根节点,让根节点根据大小再次放入链表中,重复此过程,直到只剩下两个,然后这两个创建成树。

代码就看源码吧,我把图画了下来大家可以看看。

当然现在虽然树创建完了,但是没有结束,我们为了更好的编码,最好把 code 存在它们对应位置,这样要编码时,直接找到这个数据然后输出 code 就行了。

到这里树就建完了,休息一波!!! 

编码

编码就是输出它的某一个数据对应的code,同样在输入要编码的数据时可能遇见空格,所以我们同样用 gatcher() ,读取后直接输出对应code即可。

具体请看源码。

解码

我们知道,每一个数据的 code 都不会为其他数据 code 的前缀,所以根据输入的01数字找到它对应的数据就是解码过程。但是会出现解码不成功现象,这时只要提示输入的01数字是错误的即可。

在这里我要说出一个观点,因为很多人不知到解码错误如何判断,判断什么???

对于一个树来说,我们输入的01编码总能让树到最后找到一个位置,当还有编码时,如果找到树的最后叶子节点,那就算是找到一个了,这时只要从新从根节点开始找剩下编码就好了,但是如果找到最后编码的数字没有了,但是这时没有在树存数据的地方,而且下边还能继续往下走,那就说明编码已经出现错误了。可以进行提示了。!!!

具体也请看源码。

源码

//头文件
#include <iostream>
using namespace std;


//刚开始存储每一个字母,还有符号
class words
{
private:
	char letter;//字母
	int quantity;//出现次数
public:
	words()
	{
		letter = '\0';
		quantity = 0;
	}
	void set_letter(char letter)
	{
		this->letter = letter;
	}
	void set_quantity(int quantity)
	{
		this->quantity = quantity;
	}
	char get_letter()
	{
		return this->letter;
	}
	int get_quantity()
	{
		return this->quantity;
	}
};


//存储信息链表
class tree
{
private:
	char data;//字母,还有符号
	int weight;//权重
	tree* left;//左孩子
	tree* right;//右孩子
	tree* rlink;//一条链先穿起来
	string code;//哈夫曼值 易于递归直接string相加  左0右1
public:
	tree()
	{
		data = '\0';
		weight = 0;
		left = NULL;
		right = NULL;
		rlink = NULL;
		code = "\0";
	}
	void set_data(char data)
	{
		this->data = data;
	}
	void set_weight(int weight)
	{
		this->weight = weight;
	}
	void set_left(tree* left)
	{
		this->left = left;
	}
	void set_right(tree* right)
	{
		this->right = right;
	}
	void set_rlink(tree* rlink)
	{
		this->rlink = rlink;
	}
	void set_code(string code)
	{
		this->code = code;
	}
	string get_code()
	{
		return this->code;
	}
	tree* get_rlink()
	{
		return this->rlink;
	}
	tree* get_right()
	{
		return this->right;
	}
	tree* get_left()
	{
		return this->left;
	}
	int get_weight()
	{
		return this->weight;
	}
	char get_data()
	{
		return this->data;
	}
};


//实际操作树的函数
class Tree
{
private:
	words* sav_letter[31];//存储26个字母,还有5个标点符号 ,。 ! ?
	tree* head;//先从数组存一下到链表
	tree* really_tree;//真正的树
	string record_code;//专门记录用来输出code
public:
	Tree()
	{
		//初始化存储字母数组 赋空
		for (int i = 97; i <= 122; i++)//将arr数组0-30设置为存放words结构体
		{
			sav_letter[i - 97] = new words;//分配实体空间
			sav_letter[i - 97]->set_letter(i);
			sav_letter[i - 97]->set_quantity(0);
		}
		for (int i = 26; i < 31; i++)
		{
			sav_letter[i] = new words;
			sav_letter[i]->set_quantity(0);
		}
		sav_letter[26]->set_letter('!');
		sav_letter[27]->set_letter(',');
		sav_letter[28]->set_letter(' ');
		sav_letter[29]->set_letter('.');
		sav_letter[30]->set_letter('?');
		//head初始化
		head = NULL;
		really_tree = NULL;
		record_code = "\0";
	}
	void enter_letter();//输入字母用来判断哪些字母用的多,用于构建树
	void enter_head();//把数组信息存树
	void create_tree();//创造哈夫曼树  
	void enter_code();//把code输入进去
	void enter_code_base(tree* p, string code);//把code输入进去

	void prin_code();//显示code递归两个函数实现
	void prin_code_base(tree* p);//显示code
	void prin_letter_code();//输入字母然后显示code
	void prin_letter_code_base(char letter, tree* p);//接收字母返回code
	bool prin_letter();//输入code显示字母
};


//树中函数书写
void Tree::enter_letter()//输入字母
{
	cout << "输入文段:";
	char letter = getchar();
	while (1)
	{
		if (letter == '\n')//跳出
		{
			break;
		}
		if (letter >= 97 && letter <= 122)//小写字母
		{
			sav_letter[letter - 97]->set_quantity(sav_letter[letter - 97]->get_quantity() + 1);
		}
		if (letter >= 65 && letter <= 90)//大写字母转为小写
		{
			sav_letter[letter + 32 - 97]->set_quantity(sav_letter[letter + 32 - 97]->get_quantity() + 1);
		}
		if (letter == 33)//!
		{
			sav_letter[26]->set_quantity(sav_letter[26]->get_quantity() + 1);
		}
		if (letter == 44)//,
		{
			sav_letter[27]->set_quantity(sav_letter[27]->get_quantity() + 1);
		}
		if (letter == 32)//空格
		{
			sav_letter[28]->set_quantity(sav_letter[28]->get_quantity() + 1);
		}
		if (letter == 46)//.
		{
			sav_letter[29]->set_quantity(sav_letter[29]->get_quantity() + 1);
		}
		if (letter == 63)//?
		{
			sav_letter[30]->set_quantity(sav_letter[30]->get_quantity() + 1);
		}
		letter = getchar();
	}
	system("cls");
	//给字母数组排序
	int new_qulity;//临时数量排序
	char new_letter;//临时字符排序
	for (int i = 0; i < 30; i++)          //冒泡排序,从小到大;31个字母,排30次即可
	{
		for (int k = 0; k < 30 - i; k++)
		{
			if (sav_letter[k]->get_quantity() > sav_letter[k + 1]->get_quantity())
			{
				new_qulity = sav_letter[k]->get_quantity();
				new_letter = sav_letter[k]->get_letter();
				sav_letter[k]->set_quantity(sav_letter[k + 1]->get_quantity());
				sav_letter[k]->set_letter(sav_letter[k + 1]->get_letter());
				sav_letter[k + 1]->set_quantity(new_qulity);
				sav_letter[k + 1]->set_letter(new_letter);
			}
		}
	}
	return;
}
void Tree::enter_head()
{
	tree* temporary;//临时指针
	head = new tree;//为树节点开辟实体空间
	head->set_data(sav_letter[0]->get_letter());
	head->set_weight(sav_letter[0]->get_quantity());
	temporary = head;
	for (int i = 1; i < 31; i++)
	{
		tree* new_tree = new tree;
		new_tree->set_data(sav_letter[i]->get_letter());
		new_tree->set_weight(sav_letter[i]->get_quantity());
		temporary->set_rlink(new_tree);
		temporary = temporary->get_rlink();
	}
	return;
}
void Tree::create_tree()
{
	tree* point_tree1, * point_tree2;//两个临时指针
	tree* new_tree = new tree;//开辟空间
	if (head->get_rlink()->get_rlink() == NULL)//特殊条件只剩下两个
	{
		new_tree->set_right(head->get_rlink());
		new_tree->set_left(head);
		new_tree->set_data('\0');
		new_tree->set_weight(new_tree->get_right()->get_weight() + new_tree->get_left()->get_weight());//权值赋值
		head = NULL;
		really_tree = new_tree;
		return;
	}
	point_tree2 = point_tree1 = head->get_rlink()->get_rlink();//指向head第三个后来能找到
	new_tree->set_right(head->get_rlink());//将上两个排序
	new_tree->set_left(head);
	new_tree->set_data('\0');//将新节点赋值new_tree
	new_tree->set_weight(new_tree->get_right()->get_weight() + new_tree->get_left()->get_weight());//权值赋值
	head->get_rlink()->set_rlink(NULL);//两个指针指向赋NULL
	head->set_rlink(NULL);
	int step = -1;
	if (new_tree->get_weight() < point_tree1->get_weight())//小于
	{
		new_tree->set_rlink(point_tree1);
		head = new_tree;//头节点改变
	}
	else if (new_tree->get_weight() == point_tree1->get_weight())	//等于
	{
		new_tree->set_rlink(point_tree1->get_rlink());
		point_tree1->set_rlink(new_tree);
		head = point_tree1;
	}
	else	//大于
	{
		while ((point_tree2 != NULL) && (new_tree->get_weight() > point_tree2->get_weight()))
		{
			step++;
			point_tree2 = point_tree2->get_rlink();
		}
		point_tree2 = point_tree1;
		for (int i = 0; i < step; i++)
		{
			point_tree2 = point_tree2->get_rlink();
		}
		new_tree->set_rlink(point_tree2->get_rlink());
		point_tree2->set_rlink(new_tree);
		head = point_tree1;
	}
	create_tree();
}
void Tree::enter_code()
{
	enter_code_base(really_tree, "");
}
void Tree::enter_code_base(tree* p, string code)
{
	if (p->get_right() == NULL || p->get_left() == NULL)//如果right=NULL,则left必定也为NULL
	{
		return;
	}
	p->get_left()->set_code(code + "0");
	enter_code_base(p->get_left(), p->get_left()->get_code());
	p->get_right()->set_code(code + "1");
	enter_code_base(p->get_right(), p->get_right()->get_code());
}
void Tree::prin_code()
{
	prin_code_base(really_tree);
}
void Tree::prin_code_base(tree* p)
{
	if (p == NULL)
	{
		return;
	}
	if (p->get_data() >= 97 && p->get_data() <= 122)
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	if (p->get_data() == 33)//!
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	if (p->get_data() == 44)//,
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	if (p->get_data() == 32)//空格
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	if (p->get_data() == 46)//.
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	if (p->get_data() == 63)//?
	{
		cout << p->get_data() << "     " << p->get_code() << endl;
	}
	prin_code_base(p->get_left());
	prin_code_base(p->get_right());
}
bool Tree::prin_letter()
{
	string letter;
	letter = "\0";
	tree* p;
	p = really_tree;
	char code = getchar();//输入
	while (1)
	{
		if (code == '\n')//跳出
		{
			letter = letter + p->get_data();
			if (p->get_left() != NULL || p->get_right() != NULL)//左右不为空跳出
			{
				letter = "\0";
				return false;
			}
			break;
		}
		if (p->get_left() == NULL || p->get_right() == NULL)//走一下为空则下一个循环
		{
			letter = letter + p->get_data();
			p = really_tree;
		}
		if (code == '1')
		{
			p = p->get_right();

		}
		if (code == '0')
		{
			p = p->get_left();
		}
		code = getchar();
	}
	cout << letter << endl;
	return true;
}
void Tree::prin_letter_code_base(char letter, tree* p)
{
	if (p == NULL)
	{
		return;
	}
	if (p->get_data() == letter)
	{
		record_code = p->get_code();
		return;
	}
	prin_letter_code_base(letter, p->get_left());
	prin_letter_code_base(letter, p->get_right());
}
void Tree::prin_letter_code()
{
	char letter = getchar();//输入
	while (1)
	{
		if (letter == '\n')//跳出
		{
			break;
		}
		if ((letter >= 97 && letter <= 122)||letter==63||letter==46||letter==32||letter==44||letter==33)//小写字母直接传然后输出
		{
			prin_letter_code_base(letter, really_tree);
			cout << record_code;
			record_code = "\0";//从新赋值为空,防止意外
		}
		if (letter >= 65 && letter <= 90)//大写字母转小写
		{
			prin_letter_code_base(letter + 32 - 97, really_tree);
			cout << record_code;
			record_code = "\0";
		}
		letter = getchar();
	}
	cout << endl;
	return;
}


//页面函数
void menu()
{
	cout << "                            ------------------------------------------------------------" << endl;
	cout << "                           |                       1.查看                               |" << endl;
	cout << "                           |                       2.转码                               |" << endl;
	cout << "                           |                       3.译码                               |" << endl;
	cout << "                           |                       0.退出                               |" << endl;
	cout << "                            ------------------------------------------------------------" << endl;
	cout << "                            请选择功能:";
}


//控制函数
void control()
{
	char nn;//接收无用东西
	int n;//循环所用
	Tree s;//定义树类
	bool b;
	s.enter_letter();//输入字母用来创树
	s.enter_head();//传进树里
	s.create_tree();//创建树
	s.enter_code();
	do
	{
		menu();
		cin >> n;
		switch (n)
		{
		case 1://查看
			system("cls");
			s.prin_code();
			system("pause");
			system("cls");
			break;
		case 2://转码
			system("cls");
			cout << "请输入要转码的字母:";
			nn = getchar();//接收第一个enter
			s.prin_letter_code();
			system("pause");
			system("cls");
			break;
		case 3://译码
			system("cls");
			cout << "请输入要译码的数字:";
			nn = getchar();
			b = s.prin_letter();
			if (b == false)
			{
				cout << "译码出现错误,输入格式不对" << endl;
			}
			else
			{
				cout << "译码成功" << endl;
			}
			system("pause");
			system("cls");
			break;
		case 0://退出
			break;
		default://输入错误
			cout << "选择错误" << endl;
			system("pause");
			system("cls");
		}
	} while (n != 0);

}


//主函数
int main()
{
	control();
	return 0;
}

  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 哈夫曼编码是一种无损的数据压缩算法,它将出现频率较高的字符用较短的编码表示,而出现频率较低的字符则用较长的编码表示,从而实现对文件的压缩。 对于给定的文件,首先对文件进行扫描,统计每个字符出现的频率。然后根据字符频率建立哈夫曼树,该树的构造过程是通过将频率较低的字符两两合并,生成新的节点,并将其频率设置为两个合并节点的频率之和。重复该过程,直到所有的节点都合并为一个根节点。 接下来,根据哈夫曼树构建编码表,即对每个字符赋予对应的编码,通常为0和1的串。编码的规则是:从根节点开始到每个叶子节点,左分支表示0,右分支表示1。遍历哈夫曼树,生成每个字符的编码。 最后,根据编码表,将文件中的每个字符依次替换为对应的编码,并将编码后的结果保存为压缩文件。由于频率较高的字符使用较短的编码,而频率较低的字符使用较长的编码,因此整个文件的大小会变小,实现了文件的压缩。 当需要解压缩文件时,只需用相同的哈夫曼编码表,将编码文件按照相反的方式进行解码,即可恢复原始的文件内容。 总之,哈夫曼编码是一种基于字符频率的文件压缩算法,通过构建哈夫曼树和生成编码表,实现对文件的高效压缩和解压缩。 ### 回答2: 哈夫曼编码是一种可变长度编码方法,能够有效地对文件进行压缩。在哈夫曼编码中,根据字符出现的频率,对每个字符进行编码,使得出现频率高的字符使用较短的编码,出现频率低的字符使用较长的编码。这样,压缩后的文件可以减少存储空间。 哈夫曼编码文件压缩的过程如下: 1. 统计文件中每个字符出现的频率。 2. 使用频率建立哈夫曼树。根据频率,将各个字符作为叶子节点,构建哈夫曼树。频率较低的字符位于树的较深位置,频率较高的字符位于树的较浅位置。 3. 根据哈夫曼树为每个字符生成对应的编码。从根节点出发,沿着哈夫曼树的路径,当走向左子树时,标记为0,当走向右子树时,标记为1。将所有字符的编码按照字符出现频率排序,使得频率高的字符具有较短的编码。 4. 遍历原文件,根据字符的编码进行替换。将文件中的每个字符用其对应的编码来替换,生成编码后的文件。 5. 将编码后的文件进行存储。由于使用了不同长度的编码,压缩后的文件大小比原文件小。 通过使用哈夫曼编码,文件中重复出现的字符可以用较短的编码表示,而不常出现的字符则用较长的编码表示,从而实现文件的压缩。这样,可以节省存储空间,提高文件传输速度,并减少存储的成本。 ### 回答3: 哈夫曼编码文件压缩是一种常用的数据压缩技术。它利用不同字符出现的频率来赋予其对应的最优二进制编码,以实现数据的无损压缩。 首先,对待压缩的文件进行扫描,统计每个字符的出现频率。然后,根据频率构建哈夫曼树。 构建哈夫曼树的过程是这样的:以字符出现频率作为权值,将每个字符作为一个独立的节点,构成一个森林。每次从森林中选择两个权值较小的节点合并,将合并后的节点作为新的节点插入森林,并赋予新节点的权值为两个子节点的权值之和。重复这个过程,直到森林中只剩下一个节点,即哈夫曼树的根节点。 接下来,根据哈夫曼树给每个字符赋予对应的二进制编码。从根节点开始,向左走的路径表示编码中的0,向右走的路径表示编码中的1。在每个叶子节点处停下,得到该字符的哈夫曼编码。 将哈夫曼编码写入压缩文件中,同时将对应的二进制编码写入文件内容中。这样,可以通过哈夫曼编码来还原原始文件。 压缩后的文件大小会更小,因为出现频率较高的字符使用较短的二进制编码,而出现频率较低的字符使用较长的二进制编码,达到了节省空间的目的。 但是,虽然哈夫曼编码在理论上可以达到最优压缩效果,但实际上由于文件本身特点、编码方案和算法实现等因素的限制,压缩率可能不会达到最大。因此,在实际使用中,还需要综合考虑压缩效果和压缩时间等因素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值