一、Huffman编码
1、问题描述
设要编码的字符集为{d1, d2, …, dn},它们出现的频率为{w1, w2, …, wn},应用哈夫曼树构造最优的不等长的由0、1构成的编码方案。
2、问题求解
3、举例
4、算法设计
int n;
struct HTreeNode //哈夫曼树结点类型
{ char data; //字符
int weight; //权值
int parent; //双亲的位置
int lchild; //左孩子的位置
int rchild; //右孩子的位置
};
HTreeNode ht[MAX]; //存放哈夫曼树
map<char,string> htcode; //存放哈夫曼编码
struct NodeType //优先队列结点类型
{ int no; //对应哈夫曼树ht中的位置
char data; //字符
int weight; //权值
bool operator<(const NodeType &s) const
{ //用于创建小根堆
return s.weight<weight;
}
};
void CreateHTree() //构造哈夫曼树
{ NodeType e,e1,e2;
priority_queue<NodeType> qu;
for (int k=0;k<2*n-1;k++) //设置所有结点的指针域
ht[k].lchild=ht[k].rchild=ht[k].parent=-1;
for (int i=0;i<n;i++) //将n个结点进队qu
{ e.no=i; e.data=ht[i].data;
e.weight=ht[i].weight; qu.push(e);
}
for (int j=n;j<2*n-1;j++) //构造哈夫曼树的n-1个非叶子结点
{ e1=qu.top(); qu.pop(); //出队权值最小的结点e1
e2=qu.top(); qu.pop(); //出队权值次小的结点e2
ht[j].weight=e1.weight+e2.weight; //构造哈夫曼树的非叶子结点j
ht[j].lchild=e1.no;
ht[j].rchild=e2.no;
ht[e1.no].parent=j; //修改e1.no的双亲为结点j
ht[e2.no].parent=j; //修改e2.no的双亲为结点j
e.no=j; //构造队列结点e
e.weight=e1.weight+e2.weight;
qu.push(e);
}
}
void CreateHCode() //构造哈夫曼编码
{ string code;
code.reserve(MAX);
for (int i=0;i<n;i++) //构造叶子结点i的哈夫曼编码
{ code="";
int curno=i;
int f=ht[curno].parent;
while (f!=-1) //循环到根结点
{ if (ht[f].lchild==curno) //curno为双亲f的左孩子
code='0'+code;
else //curno为双亲f的右孩子
code='1'+code;
curno=f; f=ht[curno].parent;
}
htcode[ht[i].data]=code; //得到ht[i].data字符的哈夫曼编码
}
}
5、算法证明
6、算法分析
上述算法采用了小根堆,因为从堆中删除两个结点(权值最小的两个二叉树根结点)和加入一个新结点的时间复杂度是O(log2n),这样修改后构造哈夫曼树算法的时间复杂度为O(nlog2n)。