利用哈夫曼编码进行信息通信可以大大提高信道利用率,缩短信息传输时间,降低传输成本。但是,这要求在发送端通过一个编码系统对待传数据预先编码,在接收端将传来的数据进行译码(复原)。对于双工信道(即可以双向传输信息的信道),每端都需要一个完整的编/译码系统。试为这样的信息收发站写一个哈夫曼编/译码系统。
测试数据:“THIS PROGRAM IS MY FAVORITE”
字符 |
空格 |
A |
B |
C |
D |
E |
F |
G |
H |
I |
J |
K |
L |
M |
频度 |
186 |
64 |
13 |
22 |
32 |
103 |
21 |
15 |
47 |
57 |
1 |
5 |
32 |
20 |
字符 |
N |
O |
P |
Q |
R |
S |
T |
U |
V |
W |
X |
Y |
Z |
|
频度 |
57 |
63 |
15 |
1 |
48 |
51 |
80 |
23 |
8 |
18 |
1 |
16 |
1 |
课设作业,记得没错也是蓝桥杯的比赛题目,既然写的差不多完整了就想着放出来吧。
差不多是在b站上看王卓老师把这个东西写完整的,很不错的视频。
哈夫曼树也即是最优二叉树,哈夫曼编码则是哈夫曼树的延申。
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
明白这整个关系就可以很好的理解出哈夫曼编码。
哈夫曼编码:从根出发,到达所在的结点,其路径就是哈夫曼编码。
每次经过左子树生成0,每次经过右节点生成1。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#pragma warning(disable:4996)
#pragma warning(disable:6283)
#pragma warning(disable:6001)
#pragma warning(disable:4018)
#define MAX 1000
using namespace std;
//哈夫曼树的存储表示
typedef char ElemType;
typedef struct
{
ElemType data; //结点存的数据
int weight; //结点的权值
int parent, lchild, rchild; //结点的双亲、左孩子、右孩子的下标
} HTNode, * HuffmanTree; //动态分配数组存储哈夫曼树
typedef char** HuffmanCode;//动态分配数组存储哈夫曼编码表--根据哈夫曼树求哈夫曼编码
void CreateHuffmanTree(HuffmanTree& HT, int n);
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int& n);
void HaffumanDecode(string s, HuffmanTree HT, int n);
void Select(HuffmanTree HT, int n, int& s1, int& s2);
/*---文件操作---*/
ofstream outfile("output.txt");//写入文件
ifstream readfile("output.txt");//读取文件
ofstream outCoding("Coding.txt");//写入文件
ifstream readCoding("Coding.txt");//读取文件
ofstream otable("table.txt");
ifstream rtable("table.txt");
/*有点文件操作上的bug
*/
/*哈夫曼编码器思路
哈夫曼树->哈夫曼编码器
*/
//例样输入
//THIS--PROGRAM--IS--MY--FAVORITE
//80 47 57 51 186 186 15 48 63 15 48 64 20 186 186 57 51 186 186 20 16 186 186 21 64 8 63 48 57 80 103
//菜单
void Menu()
{
cout << "**********操作循序:(1.输入哈夫曼树)->(2.初始化哈夫曼树)->(3.读取字符数据测试)*****************" << endl;
cout << "**********操作循序:(4.创建编码表)->(5.读取编码数据测试)->(6.输出编码表)***********************" << endl;
cout << "**********操作循序:(7.读取表数据测试)->(8.手动编码-转字符)->(9.手动译码-转编码)***************" << endl;
cout << "**********操作循序:(10.从文件中抽取字符进行编码-转字符)->(11.从文件中抽取字符进行译码-转编码)*" << endl;
cout << "********************0.*************************************" << endl;
cout << "********************1.输入HuffmanTree的参数***************" << endl;
cout << "********************2.检测是否初始化HuffmanTree参数*******" << endl;
cout << "********************3.创建编码表**************************" << endl;
cout << "********************4.输出编码表**************************" << endl;
cout << "********************5.手动编码-转字符*********************" << endl;
cout << "********************6.手动译码-转编码****************************" << endl;
cout << "********************7.退出********************************" << endl;
cout << "********************8.从文件中抽取字符进行编码-转字符******" << endl;
cout << "********************9.从文件中抽取字符进行译码-转编码*************" << endl;
cout << "********************10.清空文件内容************************" << endl;
cout << "********************11.读取字符数据测试********************" << endl;
cout << "********************12.读取编码数据测试********************" << endl;
cout << "********************13.读取表数据测试**********************" << endl;
cout << "***********************************************************" << endl;
cout << "请输入选择:";
}
//编码菜单
void Codingtable()
{
cout << "******TIPS:本次课题要求输入:THIS--PROGRAM--IS--MY--FAVORITE" << endl;
cout << "********题目要求编码表********" << endl;
cout << "空格-186 ,(请用-代替空格)" << endl;
cout << "A-64 B-13 C-22 D-32 E-103 " << endl;
cout << "F-21 G-15 H-47 I-57 J-1" << endl;
cout << "K-5 L-32 M-20 N-57 O-63 " << endl;
cout << "P-15 Q-1 R-18 S-51 T-80 " << endl;
cout << "U-23 V-8 W-18 X-1 Y-16 Z-1" << endl;
};
int main()
{
HuffmanTree HT = NULL;
HuffmanCode HC = NULL;
string number;
string shuru;
int n;
char c;
//c用来记录输入
while (1)
{
Menu();
flage:
string choose;
cin >> choose;
int len = choose.length();
int trans=0;
if (len == 1)
{
trans = choose[0];
}
else if (len == 2)
{
for (int i = 0;i < 2;i++)
{
int tmp = choose[i];
trans += tmp;
}
}
else
{
cout << "不是可以操作的个位数请重新输入" << endl;
goto flage;
}
switch (trans)
{
case 48:
{
cout << "暂时此功能";
break;
}
case 49:
Codingtable();
cout << "请输入结点的个数:";
cin >> n;
CreateHuffmanTree(HT, n);
break;
case 50:
if (n > 1)
{
cout << "HuffmanTree初始化完成!" << endl;
}
else
{
cout << "您给我老老实实把哈夫曼树给建了,别在咖啡厅里点蛋炒饭" << endl;
}
break;
case 51:
if (HT == NULL)
{
cout << "请先创建HuffmanTree!" << endl;
}
else
{
CreateHuffmanCode(HT, HC, n);
cout << "HuffmanCode创建完成!" << endl;
}
break;
case 52://打印编码表
if (HC == NULL)
{
cout << "请先创建HuffmanCode!" << endl;
}
else
{
cout << "输出编码表如下:" << endl;
for (int i = 1; i <= n; i++)
{
otable << "字符:" << HT[i].data << " 编码:" << HC[i] << endl;
cout << "字符:" << HT[i].data << " 编码:" << HC[i] << endl;
}
}
break;
case 53://编码
{
if (HC == NULL)
{
cout << "请先创建HuffmanCode!" << endl;
}
else
{
c = cin.get();
cout << "请输入需要转成字符的哈夫曼编码:" << endl;
string str, code;
getline(cin, code);
cout << "转成字符为:" << endl;
HaffumanDecode(code, HT, n);
}
break;
}
case 54://译码
{
if (HC == NULL)
{
cout << "请先创建HuffmanCode!" << endl;
}
else
{
//输入字符实现转码
c = cin.get();
cout << "请输入需要转成编码的字符:" << endl;
string s;
getline(cin, s);
for (int i = 0; i < s.length(); i++)
{
for (int j = 1; j <= n; j++)
{
if (HT[j].data == s[i])
{
cout << HC[j];
}
}
}
break;
}
}
case 55:
{
system("pause");
exit(0);
}
case 56://文件编码
{
string buffer;
string str,code;
if (!readCoding.is_open())
{
cout << "Error opening file"; exit(1);
}
cout << "编码为:"<> YorN;
if (YorN == 'Y')
{
outfile.is_open();
outCoding.is_open();
outfile.close();
outCoding.close();
//只写的方式打开相当于清空
cout << "清空目录咯~";
}
else if (YorN == 'N')
{
cout << "取消清空" << endl;
exit(1);
}
else
{
cout << "请输入正确的字符";
goto choice;
}
break;
}
case 98://测试字符文件
{
string buffer;
if (!readfile.is_open())
{
cout << "Error opening file"; exit(1);
}
while (getline(readfile,buffer))
{
cout << buffer;
}
break;
}
case 99://测试编码文件
{
char buffer2[MAX];
if (!readCoding.is_open())
{
cout << "Error opening file"; exit(1);
}
while (!readCoding.eof())
{
readCoding.getline(buffer2, 1000);
cout << buffer2 << endl;
}
break;
}
case 100:
{
string buffer3;
if (!rtable.is_open())
{
cout << "Error opening file"; exit(1);
}
while (getline(rtable,buffer3))
{
cout << buffer3 << endl;
}
break;
}
default:
{
cout << "能不能老老实实输入数据不要花里胡巧的?" << endl;
break;
}
}
cout << endl;
}
return 0;
}
/*----关键函数----*/
/*------构建哈夫曼树------*/
/*----Select()----*/
void Select(HuffmanTree HT, int n, int& s1, int& s2)
{
int i = 1;//开始初始化wetmin1,wetmin2,使用一个i往后找,HT[i].parent==0的结点
int wetmin1 = 100000;
int wetmin2 = 100000;//定义一个巨大的wetmin1和wetmin2
//初始化wetmin1,wetmin2,将小的赋值给wetmin1
for (i; i <= n; i++)
{
if (HT[i].parent == 0 && HT[i].weight < wetmin1)
{
wetmin2 = wetmin1;
wetmin1 = HT[i].weight;
s2 = s1;
s1 = i;
}
else if (HT[i].parent == 0 && HT[i].weight < wetmin2)
{
wetmin2 = HT[i].weight;
s2 = i;
}
}
}
//Select用于找出最小的两个结点
void CreateHuffmanTree(HuffmanTree& HT, int n)
{
outfile.is_open();
//构造哈夫曼树HT
if (n <= 1)
{
cout << "HuffmanTree节点数输入错误!" << endl;
return;
}
int m = 2 * n - 1; //数组共2n-1个元素
int s1 = 1, s2 = 1;
HT = new HTNode[m + 1]; //0号单元未用,HT[m]表示根结点
for (int i = 1; i <= m; ++i) //将1~m号单元中的双亲、左孩子、右孩子的下标都初始化为0
{
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
/*-----------------------*/
input:
string shuru;
cout << "请输入-Advance/Keyboard" << endl;
cin >> shuru;
/*-------------------预先输入-----------------------*/
if (shuru == "Advance")//预输入-方便做题用
{
string AIF1 = "THIS--PROGRAM--IS--MY--FAVORITE";
int longer1 = AIF1.length();
if (longer1 != n)
{
cout << "结点个数与预输入的结点个数不符合,即将结束程序" << endl;
exit(0);
}
for (int i = 1, j = 0; i <= longer1; ++i, ++j) //输入前n个单元中叶子结点的数据(空格用-代替)
{
HT[i].data = AIF1[j];
}
/*-----------------------*/
int AIF2[MAX] = { 80,47,57,51,186,186,15,48,63,15,48,64,20,186,186,57,51,186,186,20,16,186,186,21,64,8,63,48,57,80,103 };
int longer2 = 0;
while (AIF2[longer2] != NULL)
{
longer2++;//求数组长度用
}
for (int i = 1, j = 0; i <= longer2; ++i, ++j) //输入前n个单元中叶子结点的权值(空格用-代替)
{
HT[i].weight = AIF2[j];
}
}
/*-------------------键盘输入-----------------------*/
else if (shuru == "Keyboard")//键盘输入-会帮你将数据存入文件里头
{
cout << "请输入" << n << "个叶子结点的数据(字符):" << endl;
for (int i = 1; i <= n; ++i) //输入前n个单元中叶子结点的数据(空格用-代替)
{
cin >> HT[i].data;
outfile << HT[i].data;
}
outfile << endl;
/*------*/
cout << "请输入第" << n << "个叶子结点的权值:" << endl;
for (int i = 1; i <= n; ++i) //输入前n个单元中叶子结点的权值(空格用-代替)
{
cin >> HT[i].weight;
outfile << HT[i].weight << ' ';//一个整形 一个空格
}
}
else
{
cout << "输入有误,请重新输入";
goto input;
}
/*------------------------------------------------------------------------------------------------*/
/*--------------------------初始化工作结束,下面开始创建完整哈夫曼树---------------------------*/
for (int i = n + 1; i <= m; ++i)//合并产生n-1个结点-构造哈夫曼树
{
Select(HT, i - 1, s1, s2); //在HT[k](1<=k<=i-1)中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号s1和s2
HT[s1].parent = i;
HT[s2].parent = i; //得到新结点i,从F中删除s1,s2,将s1和s2的双亲域由0改为i
HT[i].lchild = s1;
HT[i].rchild = s2; //s1,s2分别作为i的左右孩子
HT[i].weight = HT[s1].weight + HT[s2].weight; //i的权值为左右孩子权值之和
}
outfile.close();
}
/*----------------------------*/
/*----------------------以上已经完成哈夫曼树的创建-----------------------------*/
/*----------------------以下是完成哈夫曼编码的创建-----------------------------*/
//哈夫曼编码表的的存储表示
//哈夫曼编码既是由根结点到所求结点的路径所得出的,路径在左边就得出0在右边就得出1
void CreateHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int& n)
{
outCoding.is_open();
//从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
HC = new char* [n + 1]; //分配存储n个字符编码的编码表动态空间
char* cd = new char[n]; //分配临时存放每个字符编码的动态数组空间
cd[n - 1] = '\0'; //(末尾)编码结束符
for (int i = 1; i <= n; i++) //逐个字符求哈夫曼编码
{
int start = n - 1; //start开始时指向最后,即编码结束符位置
int c = i, f = HT[i].parent;//f指向结点c的双亲结点
while (f != 0) //从叶子结点开始向上回溯,直到根结点
{
--start;
if (HT[f].lchild == c)
cd[start] = '0';//结点c是f的左孩子,则生成代码0
else
cd[start] = '1'; //结点c是f的右孩子,则生成代码1
c = f;
f = HT[f].parent; //继续向上回溯
} //求出第i个字符的编码
HC[i] = new char[n - start]; //为第i个字符编码分配空间
strcpy(HC[i], &cd[start]); //将求得的编码从临时空间cd复制到HC的当前行中
outCoding << HC[i];
}
//释放临时空间
outCoding.close();
}
//解码
void HaffumanDecode(string s, HuffmanTree HT, int n)
{
int p = 2 * n - 1;
int lens = s.size();
for (int i = 0; i < lens; i++)
{
//对编码一位一位进行查找,找到对应的叶节点为止
if (s[i] == '0')//如果碰到0,进行一次输出
{
if (HT[p].lchild != 0)
{
p = HT[p].lchild;//左边进行迭代
}
}
else if (s[i] == '1')//如果碰到1,进行输出
{
if (HT[p].rchild != 0)
{
p = HT[p].rchild;//右边进行迭代
}
}
if (HT[p].rchild == 0 && HT[p].lchild == 0)
{
//如果是叶子结点,输出,然后让p重新指向数顶,开始新一轮寻找
cout << HT[p].data;
p = 2 * n - 1;
}
}
cout << endl;
}