目录
前序
我们都知道哈夫曼编码可以有效压缩数据空间,一般可以节省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;
}