什么是哈夫曼编码?
哈夫曼编码是指给定了一段字符串下,由于字符串中每个字符会出现一定的次数,但是每个字符都会占用一定的空间,而我们为了快速访问那些出现频率高的字符,就可以用用特定的编码去取代它,进而压缩这段文本的空间。
比如这段字符串“ABCDEAAAAAA”,这里面'A'出现的频率最高,但是'A'对应的是01000001,占了8个位,为了更快访问'A'我们就可以用10这两位来表示,其他的字符也是如此,而这个10我们就可以保存在二叉树中,如图。
于是我们便可以通过每个字符出现的频率来构建二叉树,出现频率高的离根节点更近一点,这样就能更快的搜索到了。
如何构建哈夫曼树
首先把文本中每个字符出现的次数先统计一下,由于是要求使用次数来构建,所以我们要用到优先级队列,这里用STL里的priority_queue就好,注意这里默认的优先级队列默认是把值最大的先出队,所以我们要定义一个仿函数让值最小的先出队。
class Node
{
public:
Node(int wight,char value)
{
this->wight = wight;
this->value = value;
this->lchild = NULL;
this->rchild = NULL;
}
char value;//储存的字符
Node*lchild;
Node*rchild;
int wight;//出现次数
};
class Myclass
{
public:
bool operator()(Node* n1, Node* n2)
{
return n1->wight > n2->wight;
}
};
int main()
{
priority_queue<Node*,vector<Node*>,Myclass>p_q;//出现次数少的先出队列
Node*head = NULL;
system("pause");
return 0;
}
然后添加元素(我瞎写的)
void add_data(priority_queue<Node*, vector<Node*>, Myclass>&p_q)
{
p_q.emplace(new Node(10,'c'));
p_q.emplace(new Node(1,'a'));
p_q.emplace(new Node(6,'t'));
p_q.emplace(new Node(15,'x'));
p_q.emplace(new Node(20,'r'));
p_q.emplace(new Node(9,'s'));
p_q.emplace(new Node(22,'w'));
}
之后就是关键操作了,需要把队列里的2个元素a,t先出队,然后创建一个出队的2个元素的父节点,当然这个父节点的次数是二者之和,但是字符我们就赋值为' ',将创建好的这个父节点再放入优先级队列中下次再出队的元素s就肯定比A,B大,但是会比刚创建的父节点小(刚才创建的父节点的值为7),然后再把刚出队的两个元素再次创建一个父节点,依次类推……
void creat_tree(Node*&head, priority_queue<Node*, vector<Node*>, Myclass>&p_q)
{
Node*n1 = NULL;
Node*n2 = NULL;
while (!p_q.empty())
{
//将队列中队头两个元素弹出队列
n1 = p_q.top();
p_q.pop();
if (p_q.empty())//最后只剩一个节点的情况
{
head = n1;
return;
}
n2 = p_q.top();
p_q.pop();
int num = n1->wight + n2->wight;
//创建弹出两个元素的父节点
head = new Node(num, ' ');
head->lchild = n1;
head->rchild = n2;
//再将父节点放入队列中
p_q.push(head);
}
}
这样就构建好了一棵二叉树,输出下看效果
void fun(Node*head)
{
queue<Node>qq;
int c1 = 1, c2;
qq.emplace(*head);
while (!qq.empty())
{
c2 = 0;
for (int i = 0; i < c1; i++)
{
Node temp = qq.front();
qq.pop();
cout << temp.value << " "<<temp.wight<<" ";
if (temp.lchild != NULL)
{
qq.push(*temp.lchild);
c2++;
}
if (temp.rchild != NULL)
{
qq.push(*temp.rchild);
c2++;
}
}
c1 = c2;
cout << endl;
}
}
错误分析
注意构建priority_queue时里面传入的类型不能是Node这个类类型,因为任务队列在pop()的时候队列中的数据真的就消失了就算用一个指针去接受也是一个拷贝构造后的类,包括后面创建一个新的父节点再传入队列中,队列也是拷贝了一个相同的父节点,之前创建的父节点的内存就找不到了(默认是new开辟),当然左右孩子节点还是由指针管控着,可以找到,在队列中传入弹出的时候很容易造成内存泄漏,所以尽量还是使用指针类型,我是想不到解决办法才用的指针类型(释放的时候麻烦点呗)
还有就是如果用一个指针指向了优先级队列的top(),然后pop(),那最后指针是指向了删除后的优先级队列的top(),所以priority_queue传入的类型应该为指针类型,而不能是类
总结
哈夫曼树是为了压缩空间才使用的,手搓可以重新练习一下优先级队列和树,重点在那个错误分析,当时为了解决我想了1个小时,为了不用delete我一直想传入Node类型,最后没办法只能换成了指针类型,如果有大佬能用Node类型实现那就太好了!