根据哈夫曼编码的原理,编写一个程序,在用户输入结点权值的基础上求赫夫曼编码,并能把给定的编码进行译码。
(1)初始化:从键盘输入一字符串(或读入一文件),统计出现的字符和每个字符出现的频率,将字符出现的频率作为结点的权值,建立哈夫曼树。对各个字符进行哈夫曼编码,最后打印输出字符及每个字符对应的哈夫曼编码。
(2)编码:利用已建好的哈夫曼树对“输入串”进行哈夫曼编码,最后打印输入串对应的哈夫曼编码(写入文件)。
(3)译码:利用已建好的哈夫曼树对给定的一串代码进行译码,并打印输出得到的字符串。(选作)
测试数据:对字符串{casbcatbsatbat}进行编码;对电文“1101000”译码。字符集D={ ?},出现频率为w={?}
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)
#include<bits/stdc++.h>
using namespace std;
typedef struct TreeNode
{
char str;//字母
int Leistuing;//权值
int lchrild, rchrild, parent;//左孩子,右孩子,双亲
}TreeNode, Tree;
void InitTreeNode(TreeNode& T)//对树的节点进行初始化
{
T.lchrild = 0;//左孩子初始化
T.rchrild = 0;//右孩子初始化
T.parent = 0;//双亲初始化
T.str = '0';//字符
T.Leistuing = 0;//权值初始化为0
}
void Select(Tree* T, int TLength, int& S1, int& S2)//选择树中权值最小且双亲为0的两个子树
{
//寻找第一个双亲域为0且权值最小的结点
int min = 0;
//找到第一个双亲域为0的,下标暂存到min
for (int i = 0; i < TLength; ++i)
{
if (T[i].parent == 0) {
min = i;
break;
}
}
for (int i = 0; i <TLength; i++)
if (T[i].parent == 0 && T[min].Leistuing > T[i].Leistuing)
min = i;
S1 = min;
for (int i =0; i < TLength; ++i)
{
if (T[i].parent == 0 && i != S1) {
min = i;
break;
}
}
for (int i = 1; i<TLength; i++)
if (T[i].parent == 0 && T[min].Leistuing > T[i].Leistuing && i != S1)
min = i;
S2 = min;
}
bool CreatHT(Tree* T, int &TLength)//哈夫曼树的建立
{
int num = TLength;//为进行循环的次数
for (int i = 1; i < num; i++)//便利所有节点,建立哈夫曼树
{
int S1, S2;
Select(T, TLength, S1, S2);
T[TLength].Leistuing = T[S1].Leistuing + T[S2].Leistuing;//对新节点的权值进行赋值
T[S1].parent = TLength;
T[S2].parent = TLength;
T[TLength].lchrild = S1;
T[TLength].rchrild = S2;
TLength++;//为第一个空节点下标
}
return true;
}
bool decoding(char numcode[], Tree T[], char code[20][20], int Lcode[], int Tlength)
{
int temp = 0 ,bools=0;
while (numcode[temp] != '\0')
{
for (int i = 0; i < (Tlength-1)/2+1; i++)
{
for (int j = 0; j < Lcode[i]; j++)
{
if (numcode[temp] == code[i][j])
{
bools++;
temp++;
}
else
{
bools = 0;
temp = temp -j;
break;
}
}
if (bools)
{
cout << T[i].str;
}
}
}
return true;
}
int main()
{
Tree T[50];
for (int i = 0; i < 50; i++)
{
InitTreeNode(T[i]);
}
char str[20] = { '\0' };
cin >> str;
int Length = 0, TLength = 0, NumOf[26];//Length用于记录临时存储于str的字符串的长度,TLength用于存储树中新建子树个数,NumOf用于存储数字化的26个英文字母出现次数
for (int i = 0; i < 26; i++)
{
NumOf[i] = 0;
}
while (str[Length] != '\0')
{
NumOf[str[Length] - 97]++;//通过ASCII值转化,将字母转化为int型以记录其权值
Length++;
}
for (int i = 0; i < 26; i++)
{
if (NumOf[i] != 0)
{
char j = 'a' + i;
T[TLength].str = j;
T[TLength].Leistuing = NumOf[i];
TLength++;
}
}
CreatHT(T, TLength);
int Lcode[20] = {0};
char code[20][20] = { '\0' };
for (int i = 0; i <= (TLength - 1) / 2; i++)
{
int G = i;
while (T[G].parent != 0)//当未到根节点时
{
if (T[T[G].parent].lchrild == G){//判断是否为其双亲的左子树
code[i][Lcode[i]] = '0';
Lcode[i]++;
}
if (T[T[G].parent].rchrild == G){//判断是否为其双亲的右子树
code[i][Lcode[i]] = '1';
Lcode[i]++;
}
G = T[G].parent;//往上找节点
}
}
cout << "字符" << " " << "权" << " " << "双亲" << " " << "左孩子" << " " << "右孩子"<<"\n";
for (int i = 0; i < TLength; i++)
{
cout << T[i].str << " " << T[i].Leistuing << " " << T[i].parent << " " << T[i].lchrild << " " << T[i].rchrild << "\n";
}
for (int i = 0; i <=(TLength-1)/2; i++)//因为原编码为反着的的,改程序用于对编码倒置
{
for (int j = 0; j <Lcode[i]/2; j++)
{
char temp;
temp = code[i][j];
code[i][j] = code[i][Lcode[i]-1 - j];
code[i][Lcode[i]-1- j] = temp;
}
}
cout << "编码为:\n";
for (int i = 0; i<(TLength-1)/2+1 ;i++)
{
cout << T[i].str << " ";
for (int j = 0; j < 20; j++)
{
cout << code[i][j];
}
cout << "\n";
}
char numcode[20] = { '\0' };
cout << "请输入要翻译的编码\n";
cin >> numcode;
cout << "译码为:\n";
decoding(numcode, T, code, Lcode, TLength);
cout << "字符集D的出现频率为W={";
for (int i = 0; i < (TLength - 1) / 2 + 1; i++)
{
cout << T[i].str << ":" << T[i].Leistuing << ",";
}
cout << "}";
return 0;
}
运行结果如图: