赫夫曼树的定义
基本概念
(1)节点之间的路径:一个结点到另一个结点,所经过节点的结点序列。
(2)结点之间的路径长度:结点之间路径上的分支数(边),如汽车到下一站的路径长度为1。
(3)树的路径长度:从根结点到每个叶子结点的路径长度之和。
(4)带权路径: 路径上加上的实际意义。如汽车到下一站的距离我们叫做权值
(5)树的带权路经长度:每个叶子结点到根的路径长度*权值 之和,记作WPL。
(6)使二叉树的带权路径(WPL)最小的树 ,我们叫做哈夫曼树。
赫夫曼树的构造方法
假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
如:对 下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下:
赫夫曼编码
定义
规定赫夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过到路径分支组成到0和1到序列便为该结点对应字符到编码。
赫夫曼树的实现
实现思路
(1).遍历整个字符串,计算出每个字符的出现频率
(2).将所有的字符的频率作为权值存在赫夫曼树结构体中
(3).从所有的赫夫曼树挑选两个权重最小的结点并组成新结点,并标记这两个使用过的结点
(4).重复(3)直到所有结点都已经加入到赫夫曼树中
(5).根据左子树为0,右子树为1,从叶子结点反向计算出赫夫曼编码
(6).注意!!赫夫曼编码并不是唯一的,会根据结点在赫夫曼树中位置不同而改变
实现代码
赫夫曼树的存储结构
//int占4个字节,最大为2147483647
#define MAX 2147483647 (全局变量)
//代表赫夫曼树结点的结构体
struct HTNode
{
int weight;
int parent;
int lchild, rchild;
HTNode(int val = 0) : weight(val)
{
parent = 0;
lchild = -1;
rchild = -1;
}
};
存储字符和其出现频率的结构体
struct LetterFre
{
char ch; //存储字符
int count; //出现的频率
};
统计字符串中字符频率
//计算每个字符出现的频率
vector<LetterFre> CntFrequenceOfLetter(string str)
{
vector<LetterFre> InfoList; //存储字符频率信息
if (str.size() == 0)
return InfoList;
sort(str.begin(), str.end());
char s = str[0];
int count = 0;
for (int i = 0; i < str.size(); i++)
{
if (str[i] != s)
{
//将统计的信息存入结构体
LetterFre letter;
letter.ch = s;
letter.count = count;
InfoList.push_back(letter);
//开始统计下一个字符
s = str[i];
count = 1;
}
else
{
count++;
}
//如果为最后一个字符
if (i == str.size() - 1)
{
//将统计的信息存入结构体
LetterFre letter;
letter.ch = s;
letter.count = count;
InfoList.push_back(letter);
}
}
return InfoList;
}
从结点中找到权重最小的两个结点的索引
//从森林中选择权重最小的两棵树
void SelectTree(vector<HTNode> *T, int *s1, int *s2)
{
int min = MAX;
//选出第一小的
for (int i = 0; i < T->size(); i++)
{
if ((*T)[i].parent == 0 && (*T)[i].weight <= min)
{
min = (*T)[i].weight;
*s1 = i;
}
}
min = MAX; //MAX为全局变量
//选出第二小的
for (int i = 0; i < T->size(); i++)
{
if ((*T)[i].parent == 0 && (*T)[i].weight < min && *s1 != i)
{
min = (*T)[i].weight;
*s2 = i;
}
}
}
构建赫夫曼树
//构造赫夫曼树
vector<HTNode> CrtHuffmanTree(vector<LetterFre> InfoList)
{
vector<HTNode> HTNodeList(InfoList.size());
if (HTNodeList.size() == 0)
return HTNodeList;
else
{
for (int i = 0; i < InfoList.size(); i++)
{
HTNodeList[i].weight = InfoList[i].count;
}
for (int j = InfoList.size(); j < 2 * InfoList.size() - 1; j++)
{
int s1 = 0, s2 = 0;
//找到森林中权重最小的树
SelectTree(&HTNodeList, &s1, &s2);
//cout << s1 << " " << s2 << endl;
//合并成新结点
HTNode NewNode(HTNodeList[s1].weight + HTNodeList[s2].weight);
NewNode.lchild = s1;
NewNode.rchild = s2;
HTNodeList[s1].parent = HTNodeList.size();
HTNodeList[s2].parent = HTNodeList.size();
HTNodeList.push_back(NewNode);
}
}
return HTNodeList;
}
生成赫夫曼编码
void CrtHuffmanCode(vector<LetterFre> list1, vector<HTNode> list2)
{
vector<int> code;
for (auto i = 0; i < list1.size(); i++)
{
cout << list1[i].ch << ": ";
auto crr = i;
while (list2[crr].parent != 0)
{
auto parent = list2[crr].parent;
if (crr == list2[parent].lchild)
code.push_back(0);
else if (crr == list2[parent].rchild)
code.push_back(1);
crr = list2[crr].parent;
}
//反向遍历容器
for (auto it = code.rbegin(); it != code.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
code.clear();
}
}
实例
int main()
{
string s = "AAABBBBCCCCCCCCCCDDDDDDDDEEEEEEFFFFF";
auto list = CntFrequenceOfLetter(s);
auto TreeList = CrtHuffmanTree(list);
CrtHuffmanCode(list, TreeList);
/*输出为
A: 0 0 0
B: 0 0 1
C: 1 0
D: 0 1
E: 1 1 1
F: 1 1 0
*/
}