一、实验目的
1. 熟悉贪心算法的基本思想;
2. 使用贪心策略分析、解决问题
3. 实现算法。
二、实验内容
- 分析哈夫曼编码
- 实现哈夫曼编码的算法
三、问题分析
哈夫曼编码算法使用字符在文件中出现的频率表来建立一个用0、1串表示各字符的最优表示方式。
选取概率最小的两个字符,分别赋予编号0和1,构建出一颗二叉树,其中两个字符分别作为叶子节点,它们的父节点包含了它们出现的概率之和,继续合并两个最小权值的结点直到所有的字符都能被编码到二叉树上。
对于每个字符,从根节点开始,向左走则编码为0,向右走则编码为1,记录下该字符的编码。将所有字符的编码连接起来,即得到了该字符串的哈夫曼编码。
四、算法描述
哈夫曼编码算法采取的贪心策略是每次从树的集合中取出没有双亲且权值最小的两棵树作为左右子树,构造一棵新树,新树根节点的权值为其左右子树结点权值之和,将新树插入到树的集合中。
哈夫曼编码算法步骤:
1. 统计字符出现频率:对给定的数据进行扫描,统计每个字符出现的频率。
2. 构建哈夫曼树:将所有的字符看作是一个森林,每个字符可以看作是一棵只有一个节点的树。从中选择两个没有双亲且权值最小的树合并成一棵新树,新树的权值为两个结点的权值之和。
3.重复步骤2,直到所有的字符都能被编码到二叉树上。
4. 编码:根据构建出来的哈夫曼树,对每个字符进行编码。规定在树中左分支表示0,右分支表示1,从根节点到叶子节点路径上经过的所有分支组成的字符串即为该叶子节点所代表的字符的编码。
五、源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
//霍夫曼树的定义
typedef struct
{
int weight; //权值
int parent, lchild, rchild; //每个结点的双亲、左右孩子的数组下标
}HNode, *HuffmanTree;
typedef char * * HuffmanCode;//动态分配数组存储霍夫曼树
//Select算法 选择权值最小的两颗树
void Select(HuffmanTree &H, int n, int &i1, int &i2)
{
int min1=1000;
int min2=1000;
int pos1=0;
int pos2=0;
for (int i=1;i<n+1;++i)
{
if (H[i].parent == 0)
{
if (H[i].weight<min1)
{
min2=min1;
pos2=pos1;
min1=H[i].weight;
pos1=i;
}else if (H[i].weight<min2)
{
min2=H[i].weight;
pos2=i;
}
}
}
i1=pos1;
i2=pos2;
}
//霍夫曼树的初始化
void InitHTree(HuffmanTree &H,int n)
{
H=new HNode[2 * n];//开辟新的结点空间
for(int i=1;i<2 * n;++i)
{
H[i].parent=H[i].lchild=H[i].rchild = 0;//将双亲和左右孩子初始化为0
}
for(int i=1;i<=n;++i)
{
cin>>H[i].weight;//输入前n个元素的weight值
}
}
//霍夫曼树的构造
void CreatHuffman(HuffmanTree &H,int length)
{
//对霍夫曼树进行初始化
InitHTree(H, length);
//找出当前森林中最小的两棵树,创建新树,并让原来的两个树作为新树的孩子
for(int i=length+1;i<2*length;++i)
{
int i1=0,i2=0;
Select(H,i-1,i1,i2);//在H[k](1<k<i-1)中选择两个其双亲域为0,
//且权值最小的结点,并返回它们在H中的序号i1和i2
H[i].weight=H[i1].weight+H[i2].weight;//i 的权值为左右孩子权值之和
H[i1].parent=H[i2].parent=i;
H[i].lchild=i1;
H[i].rchild=i2;//i1,i2分别作为i的左右孩子
}
}
//求霍夫曼编码
HuffmanCode CreatHuffmanCode(HuffmanTree H, int n)
{
//从叶子到根逆向求每个字符的霍夫曼编码,存储在编码表HC中
HuffmanCode HC=(char **)malloc(sizeof(char *) * (n + 1));
char *temp_string=(char *)malloc(sizeof(char) * n); //此数组使用0号下标
temp_string[n-1]='\0'; //临时数组,存放字符串的最后一个位置 '\0'
for(int i= 1;i<n+1;++i)
{
int parent=H[i].parent;//需要向上回溯
int current=i;//回溯中当前节点
int start=n-1;//数组中最后一个位置,即'\0'
while(parent)
{
if (H[parent].lchild == current)
{
temp_string[--start]='0';//如果是左孩子,那么为'0'
}
else
temp_string[--start]='1';//右孩子为 '1'
current=parent;
parent=H[parent].parent;
}
//计算长度
HC[i]=(char *)malloc(sizeof(char) * (n - start)); //根据长度分配空间
strcpy(HC[i], &temp_string[start]); //拷贝字符串
}
free(temp_string);//释放堆空间
return HC;
}
int main()
{
HuffmanTree T;
int n=0;
cout<<"请输入字符数量:";
cin>>n;
cout<<"请输入各个字符的权值:";
CreatHuffman(T,n);
for (int i=1;i<2 * n;++i)
{
printf("i=%d\tweight=%d\tparent=%d\tlchild=%d\trchild=%d\n",i,T[i].weight,T[i].parent,T[i].lchild,T[i].rchild);
}
HuffmanCode Hc=CreatHuffmanCode(T,n);
int a = 65;
cout<<"霍夫曼编码如下:"<<endl;
for (int i=1;i<n+1;++i)
{
printf("%c=%s\n",a,Hc[i]);
++a;
}
return 0;
}
六、运行结果
请输入字符数量:6
请输入各个字符的权值:5 32 18 7 25 13
i=1 weight=5 parent=7 lchild=0 rchild=0
i=2 weight=32 parent=10 lchild=0 rchild=0
i=3 weight=18 parent=9 lchild=0 rchild=0
i=4 weight=7 parent=7 lchild=0 rchild=0
i=5 weight=25 parent=9 lchild=0 rchild=0
i=6 weight=13 parent=8 lchild=0 rchild=0
i=7 weight=12 parent=8 lchild=1 rchild=4
i=8 weight=25 parent=10 lchild=7 rchild=6
i=9 weight=43 parent=11 lchild=3 rchild=5
i=10 weight=57 parent=11 lchild=8 rchild=2
i=11 weight=100 parent=0 lchild=9 rchild=10
霍夫曼编码如下:
A=1000
B=11
C=00
D=1001
E=01
F=101