实验九:构造哈夫曼树和生成哈夫曼编码
实验内容:构造一棵哈夫曼树,输出相应的哈夫曼编码以及平均查找长度,并针对一条消息进行编码以及解码。
#include<iostream>
#include<string.h>
using namespace std;
#define MAXSIZE 100
typedef struct // 哈夫曼数的结构体
{
int weight; // 结点的权值
int parent, lchild, rchild; // 结点的双亲 左孩子 右孩子
}HTNode, * HuffmanTree;
typedef char** HuffmanCode; // 动态分配数组存储哈夫曼编码表
void Select(HuffmanTree HT, int t, int &s1, int &s2)
{ // 在哈夫曼树HT中 查找HT[k](1<=k<=i-1)
// 选择其中两个双亲域为0且权值最小的结点 将序号保存s1和s2中
for (int i = 1; i <= t; i++) // 初始化s1与s2的值
{
if (HT[i].parent == 0) // 判断双亲域是否为0
{
s1 = s2 = i;
break;
}
}
int temp; // 临时存储较小值
for (int i = 1; i <= t; i++) // s1始终小于s2
if (HT[i].parent == 0) // 判断双亲域是否为0
{
if (s1 == s2) // 处理s1与s2相等的情况(初始条件)
if (HT[i].weight < HT[s1].weight) // 将小值赋值给s1
s1 = i;
else
s2 = i;
else if (HT[i].weight < HT[s1].weight && HT[i].weight < HT[s2].weight)
{// 当前权值比s1与s2的权值都小时 按照s1始终小于s2赋值
s2 = s1;
s1 = i;
}
else if (HT[i].weight > HT[s1].weight && HT[i].weight < HT[s2].weight)
s2 = i; // 当前权值只小于s2的权值时 直接赋值给s2
}
}
void HT_OutPut(HuffmanTree HT, int n) // 输出哈夫曼树的存储结构
{
cout << ">> 该哈夫曼树的存储结构如下:" << endl;
cout << "\t结点\t权重\t父亲\t左孩子\t右孩子" << endl;
for (int i = 1; i < 2*n; i++)
cout << "\t "
<< i
<< "\t "
<< HT[i].weight
<< "\t "
<< HT[i].parent
<< "\t "
<< HT[i].lchild
<< "\t "
<< HT[i].rchild
<< endl;
}
void CreateHuffmanTree(HuffmanTree &HT, int n) // 构造哈夫曼树 HT
{
/*********初始化哈夫曼树:初始化 + 输入权值*********/
if (n <= 1)
return;
int m = 2 * n - 1; // m 为要储存的总结点数
HT = new HTNode[m + 1]; // 申请 2n 个结点空间
// 注意:HT[m]代表根结点
for (int i = 0; i <= m; i++)
// 将1~m号单元中双亲、左孩子和右孩子的下标都初始化为0
{
HT[i].weight = 0;
HT[i].parent = 0;
HT[i].lchild = 0;
HT[i].rchild = 0;
}
cout << "依次输入叶子结点的权值: ";
for (int i = 1; i <= n; i++) // 依次输入前n个单元中叶子结点的权值
cin >> HT[i].weight;
getchar();
/********创建哈夫曼树:n-1次选择、删除、合并********/
for (int i = n + 1; i <= m; i++)
{
int s1, s2; // 临时储存结点的序号
Select(HT, i - 1, s1, s2); // 调用函数进行选择操作
// 将s1、s2两个结点的双亲域修改为确定的序号i (删除操作)
HT[s1].parent = i;
HT[s2].parent = i;
// 将s1、s2设置为i的孩子 (合并操作)
HT[i].lchild = s1;
HT[i].rchild = s2;
// 确定序号为 i 的结点的权值
HT[i].weight = HT[s1].weight + HT[s2].weight;
}
}
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode& HC, int n)
{
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;
int f = HT[i].parent; // f指向结点c的双亲结点
while (f != 0) // 从叶子结点向上回溯 直到根结点
{
start--; // 回溯一次start向前指一个位置
if (HT[f].lchild == c) // 结点c是f的左孩子
cd[start] = '0'; // 生成代码0
else // 结点c是f的右孩子
cd[start] = '1'; // 生成代码1
c = f;
f = HT[f].parent; // 向上回溯
}
HC[i] = new char[n - start]; // 为第i个字符编码分配适当大小的空间
strcpy(HC[i], &cd[start]); // 将所求得的编码从cd空间复制到HC的当前行中
}
delete cd; // 释放临时空间
}
void HC_OutPut(HuffmanCode HC, int n) // 输出哈夫曼编码表及平均查找长度
{
int length = 0;
cout << ">> 下面是哈夫曼编码表: " << endl;
for (int i = 1; i <= n; i++) // 遍历编码表的每一行
{
int j = 0;
cout << "\t编码" << i << ": ";
while (HC[i][j] != '\0') // 对编码表的某行进行遍历输出
{
cout << HC[i][j++];
length++;
}
cout << endl;
}
cout << "\t>> 平均查找长度为: " << (double)length / n << endl;
}
void TransHuffmanCode(HuffmanTree HT,int n, char* cd) // 译码操作
{
int start; // 存储根结点的序号
for (int i = 1; i <= 2 * n - 1; i++) // 寻找根结点 并给start赋值
if (HT[i].parent == 0)
{
start = i;
break;
}
int p = start; // 定义临时变量p 存储当前的遍历序号
cout << "译码后的结果: ";
while (*cd != '\0') // 遍历所有编码
{
if (HT[p].lchild == 0 && HT[p].rchild == 0) // 判断是否是叶子结点
{
cout << HT[p].weight << " "; // 如果是叶子结点 则输出
p = start; // 同时 当前序号复位为根结点序号
}
if (*cd == '0') // 判断遍历方向(是否遍历左孩子)
p = HT[p].lchild;
else if (*cd == '1') // 判断遍历方向(是否遍历右孩子)
p = HT[p].rchild;
cd = cd + 1; // 遍历下一个编码
}
cout << endl;
}
int main()
{
HuffmanTree HT; // 定义一个哈夫曼树
HuffmanCode HC; // 定义一个编码表
char cd[MAXSIZE], ch;
int n; // 存储叶子结点个数
cout << "请输入您生成的哈夫曼树的叶子结点个数: ";
cin >> n;
CreateHuffmanTree(HT, n); // 生成哈夫曼树
CreatHuffmanCode(HT, HC, n); // 生成哈夫曼编码表
HT_OutPut(HT, n); // 输出哈夫曼树
HC_OutPut(HC, n); // 输出哈夫曼编码表及平均查找长度
cout << "请在上述哈夫曼编码中选择你要译码的编码: ";
int i = 0;
while ((ch = getchar()) != '\n') // 接收输入的编码
cd[i++] = ch;
cd[++i] = '\0'; // 编码结束的标志
TransHuffmanCode(HT, n, cd); // 译码操作
system("pause");
return 0;
}