前言
最近有很多人问有关哈夫曼树编码的问题,今天得空在这里写下自己的对于哈夫曼树学习的有关收获。文章仅供参考学习。
哈夫曼树的定义
将树中的结点赋予一个有某种意义的数值,称此数值为该结点的权。从树根结点到该结点之间的路径长度与该结点上权的乘积称为结点的带权路径长度。树中所有叶子结点的带权路径长度之和称为该树的带权路径长度,通常记为:
其中,n0表示叶子结点的数目,wi和li(1≤i≤n0)分别表示叶子结点ki的权值和根ki之间的路径长度(即从叶子结点到达根结点的分支数)。
在由n0个带权叶子结点构成的所有二叉树中,带权路径长度WPL最小的二叉树称为哈夫曼树(或最优二叉树)。
哈夫曼树构造算法
哈夫曼算法:
根据给定的n0个权值W=(w1,w2,…wn0),对应结点构成n0棵二叉树的森林T=(T1,T2,…Tn0),其中,每棵二叉树Ti(1≤i≤n0)中都只有一个带权值wi的根结点,其左、右子树均为空。
在森林T中选取两棵结点的权值最小的子树分别作为左、右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根的权值之和。
在森林T中,用新得到的二叉树代替这两棵树。
重复2和3,直到T只含一棵树为止,这棵树便是哈夫曼树
定理:
对于具有n0个叶子结点的哈夫曼树,共有2n0-1个结点。
哈夫曼编码
设需要编码的字符集合为{d1,d2,…,dn0},各字符在电文中出现的次数集合为{w1,w2,…,wn0},以d1,d2,…,dn0作为叶子结点,以w1,w2,…,wn0作为各根结点到每个叶子结点的权值构造一棵哈夫曼树,规定哈夫曼树中的左分支为0,右分支为1,则从根结点到每个叶子结点所经过的分支对应的由0和1组成的序列便成为该结点对应字符的编码,称为哈夫曼编码。
哈夫曼算法实现
定义节点类
#include <iostream>
#include <stack>
#define MAX 100
#define MAXWEI 1000
using namespace std;
class Node{
public:
char data; //结点值
double weight; //权值
int parent; //双亲结点
int lChild; //左孩子结点
int rChild; //右孩子结点
};
定义哈夫曼编码类
class HCode{
public:
stack<char> cd; //存放当前结点的哈夫曼编码
};
定义哈夫曼树类
class Huffman{
public:
void setValue(char*,double[],int);//设置初始值
void createHuff();//构造哈夫曼树
void createCode();//根据哈夫曼树求哈夫曼编码
void dispHCoude();//输出哈夫曼编码
private:
int n;//权值和叶子结点的个数
Node ht[MAX];//存放哈夫曼树
HCode hcd[MAX]; //存放哈夫曼编码
};
设置初始值
void Huffman::setValue(char *str,double d[],int m){
//str表示结点,d表示权值,两者一一对应,m表示个数
n = m;
for(int i=0;i<m;i++){
Node* n = new Node();
n->data = str[i];
n->weight = d[i];
ht[i] = *n;//将设置好值的结点存入ht数组中
}
}
构造哈夫曼树
void Huffman::createHuff(){
int i,k,lNode,rNode;
double min1,min2;
for(i=0;i<(2*n-1);i++){//为所有结点的相关域置初值-1
ht[i].parent = -1;
ht[i].lChild = -1;
ht[i].rChild = -1;
}
for(i=n;i<(2*n-1);i++){//构造哈夫曼树,仅求非叶子结点
min1=min2=MAXWEI;//初始时设置最大权值
lNode=rNode=-1;//lNode,rNode为权值最小的结点位置
for(k=0;k<=(i-1);k++){//循环条件根据外层循环改变而改变,是在ht数组中寻找权值最小的两个结点
if(ht[k].parent==-1){
if(ht[k].weight<min1){
min2 = min1;
rNode = lNode;
min1 = ht[k].weight;
lNode = k;
}else if(ht[k].weight<min2){
min2 = ht[k].weight;
rNode = k;
}
}
}
ht[lNode].parent = i;
ht[rNode].parent = i;
ht[i].weight = ht[lNode].weight + ht[rNode].weight;
ht[i].lChild = lNode;
ht[i].rChild = rNode;
}
}
由哈夫曼树求出哈夫曼编码
void Huffman::createCode(){//从叶子结点向树根结点编码
int i,f,c;
for(i=0;i<n;i++){//只遍历叶子结点
c = i;
f = ht[i].parent;
hcd[i].cd.push('#');//将栈底置为‘#’,方便输出
while(f!=-1){//循环到达树根结点
if(ht[f].lChild == c){//当前结点是双亲结点的左孩子结点
hcd[i].cd.push('0');
}else{//是双亲结点的右孩子结点
hcd[i].cd.push('1');
}
c = f;//再对双亲结点进行同样的操作
f = ht[f].parent;
}
}
}
输出哈夫曼编码
void Huffman::dispHCoude(){
for(int i=0;i<n;i++){
int j=0;
cout << ht[i].data << "结点的编码为:" ;
while(hcd[i].cd.top()!='#'){//编码全部输出完的条件
char c = hcd[i].cd.top();
cout << c;
hcd[i].cd.pop();
}
cout << endl;
}
}
主函数
总结
以上仅供参考学习,侵权联系删除,原文链接:https://blog.youkuaiyun.com/Bob______/article/details/111397400