哈夫曼树的主要用途就是来生成字符对应的哈夫曼编码以达到压缩文件的目的。
哈夫曼树的每个叶节点的权值代表该字符在文本中出现的次数,每个父节点的权值等于左子树叶节点与右子树叶节点的权值之和,那么根节点的权值是所有字符出现的总次数。
那么如何得出哈夫曼编码?我们设向右走为1, 向左走为 0。那么我们可以这样表示字符:
则a表示为0。
我们从下向上来构建这棵树的话。有2种思路,你可以构建成这样
这就是我们为什么引入权值的原因。我们用第二种思路:
那么我们如何构建这棵树?从图中看出,在最小面的叶节点权值最小,我们可以利用优先级队列(也可以直接使用最小堆,如果你的最小堆实现了足够的函数),权值最小的优先出列。在读取完字符出现的次数后,构建成一个节点并将对应指针压入队列中。
我们每次取出2个权值最小的节点指针,我们构建一个节点作为它们的父节点,其权值为左右子节点权值之和,并将这个新节点压入队列中,代码如下:
//d里面已经存入了字符构成的节点指针了
Node *construct(Plie<Node*, compare<Node*>>& d)
{
Node *root = NULL;
while (d.size() > 1)
{
//将节点连接起来
root = new Node();
Node *left = root->_left = d.top();
d.pop();
Node *right = root->_right = d.top();
d.pop();
root->_time = left->_time + right->_time;
left->_parent = root;
right->_parent = root;
d.push(root);
}
return root;
}
全部代码如下:
void trans(const string str)
{
errno_t error;
FILE* file;
error = fopen_s(&file, str.c_str(), "r");
if (error)
{
throw 1;
}
Node *root = NULL;
Plie<Node*, compare<Node *>> d;
vector<unsigned long > count(256, 0);
map<int, string> contrast;
//统计字符次数
int va;
while ((va = fgetc(file)) != EOF)
{
count[va]++;
}
//将出现过的字符转化为Node
for (int i = 0; i < 256; i++)
{
if (count[i] != 0)
{
Node *p = new Node(i, count[i]);
d.push(p);
}
}
//构建哈夫曼树
root = construct(d);
//记录字符所对应的新编码
Tran(root, contrast);
}
//上面的函数会调用下面的函数
Node *construct(Plie<Node*, compare<Node*>>& d)
{
Node *root = NULL;
while (d.size() > 1)
{
//将节点连接起来
root = new Node();
Node *left = root->_left = d.top();
d.pop();
Node *right = root->_right = d.top();
d.pop();
root->_time = left->_time + right->_time;
left->_parent = root;
right->_parent = root;
d.push(root);
}
return root;
}
void Tran(Node *root, map<int, string>& contrast)
{
string s;
traning(root, s, contrast);
}
void traning(Node *root, string s, map<int, string>& contrast)
{
if (NULL == root->_left && NULL == root->_right)
{
contrast[root->_c] = s;
return;
}
//向左走加0,向右走加1
traning(root->_left, s + '0', contrast);
traning(root->_right, s + '1', contrast);
}