###哈夫曼树###
显然两种判断树的效率不同,所以哈夫曼树就是研究最优二叉树
路径:从树的一个结点到另一个结点的分支构成了两个结点间的路径(简单来说就是两个结点之间的连线)
结点的路径长度:两个结点间路径上的分支数
树的路径长度:从根到每一个结点的路径长度之和,记作:TL
结点数目相同的二叉树中,完全二叉树是路径最短的二叉树,反之不成立
权(weight):将树中结点赋给一个有着某种含义的数值,则这个数值称为该节点的权
结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积
树的带权路径长度:树中所有叶子结点的带权路径长度之和
由此可以引出哈夫曼树的定义
哈夫曼树最优树:带权路径长度(WPL)最短的树;前提是树的度是一样的(要么比较二叉树,要么比较三叉树)
哈夫曼树最优二叉树:带权路径长度(WPL)最短的二叉树
构造哈夫曼树的算法就是哈夫曼算法,哈夫曼树不唯一
那么显然权越大的叶子离根越近路径越短
###构造哈夫曼树###
利用贪心算法:构造哈夫曼树时首先选择权值小的叶子结点,权值大的最后构造,这样就离根近了
-
根据n个给定的权值{wi}构造n棵二叉树森林F={Ti},其中Ti是只有一个带权为wi的根结点
-
在F中选用两个最小作为左右子树造一棵新二叉树,然后把这两个小树从森林中去除,再在森林中加上这棵新树(其权值为两小树的权值和)
-
重复第2步,直到森林中只有一棵树为止,这棵大树就是哈夫曼树了
构造森林全是根 选用两小造新树 删除两小添新树 重复2步到哈夫
哈夫曼树种只有度(该结点有几个分支)为0和度为2的结点,没有度为1的结点
包含n棵树的森林要经过n-1次合并才可以形成哈夫曼树,共产生n-1个新的结点(如图空白结点),所以n个叶子结点的哈夫曼树中共有2n-1个结点
###哈夫曼树构造###
哈夫曼树构造算法,采用顺序存储结构——一维结构数组
n个叶子的哈夫曼树有2n-1个结点,因此构造结构数组2n个单位,第一个不用
//结点类型定义
typedef struct {
int weight;
int parent, lch, rch;
}HTNode, *HuffmanTree;
-
第一步 构造森林全是根,它们的孩子全部都是0,0位置没用,因此表示为没有孩子
-
第一步 构造森林全是根,它们的孩子全部都是0,0位置没用,因此表示为没有孩子
-
第二步 选择两小造新树,给最小的两个的parent赋值成新根坐标,表示它们有双亲因此不是根
-
第三步 删除两小添新树,给新节点赋予权重为两小之和
-
第四步 重复二部到哈夫,当表中所有结点只有一个parent为0时结束,此时坐标为2n-1
//哈夫曼树构造算法实现
void CreatHuffmanTree(HuffmanTree, int n) {//构造哈夫曼树——哈夫曼算法
if (n<=1) return;
int m=2*n-1;//数组共有m个元素
HuffmanTree HT=new HTNode[m+1]; //0号不用,HT[m]表示根结点
for (int i=1; i<=m; i++) {
HT[i].lch=HT[i].rch=HT[i].parent=0;
}//将2n-1个元素的lch,rch,parent初始化为0
for (int i=1; i<n; i++) cin >> HT[i].weight;//输入n个元素的权重
//初始化步骤结束,接下来开始建立哈夫曼树
//在parent为0的元素中选取2个最小的
for (int i=n+1; i<=m; i++) {//合并产生n-1个结点——构造哈夫曼树
Select(HT, i-1, s1, s2);
//在HT[K](1<=K<=i-1)中选择两个其双亲域是0,且权重最小的结点,返回它们的序列号s1,s2
HT[s1].parent=HT[s2].parent=i;//表示F中删除s1,s2
HT[i].lch=s1; HT[i].rch=s2;//s1,s2分别作为i的左孩子和右孩子
HT[i].weight=HT[s1].weight+HT[s2].weight;//i的权重为左右孩子权重之和
}
}
###哈夫曼树的应用###
哈夫曼编码
在传输时,将字符转换成二进制字符串,如传输ABACCDA
如果令:A----00, B----01, C----10, D----11
这种编码方式将编码设置为长度相同的二进制编码,常常浪费空间和时间
解决方法就是将编码设置为长度不等的二进制编码,即让待传字符串中出现次数较多的字符采用尽可能短的编码,则转换的二进制字符串就可以减少传输空间和传输时间
为了解决重码问题,关键就是要设计长度不等的编码,且必须使得任一字符的编码都不是另一字符编码的前缀,这种编码称为前缀编码
那么给定一颗哈夫曼编码二叉树,如何求哈夫曼编码?
-
从叶子向上爬到根,再倒过来,就是哈夫曼编码
-
看当前结点的parent结点中,自己是左孩子还是右孩子,如果是左孩子则为0,否则为1
void CreatHuffmanCode(HuffmanTree HT, HuffmanCode &HC, int n) {
//从叶子到根逆向求每一个字符的哈夫曼编码,储存在编码表HC中
HC=new char*[n+1];
//分配n个字符编码的头指针矢量,指针数组,new一块n个单位char*类型的空间
cd=new char[n];//分配临时存放编码的动态数组空间
//n个元素,哈夫曼树有可能n层,那分支线最多n-1条,最后一格存放'\0'
cd[n-1]='\0';//编码结束符
for (int i=1; i<=n; i++) {//逐个字符求哈夫曼编码
int start=n-1, c=i, f=HT[i].parent;
while (f!=0) {//从叶子结点向上回溯,直到根结点
start--;//回溯一次start就向前指一个位置
if (HT[f].lchild==c) cd[start]='0';//结点c是f的左孩子,则生成代码0
else cd[start]='1';//结点c是f的右孩子,则生成代码1
c=f; f=HT[f].parent;//继续向上回溯
}//求出了第i个字符的编码
HC[i]=new char[n-start];//为第i个字符的编码分配空间
strcpy(HC[i], &cd[start]);//将求得的编码从临时空间cd非制导HC的当前行中
//strcpy是用于把一个char*类型复制到另一个地址空间
//PS:string类转成char*类型方法:char* c; string s="1234"; c = s.c_str();
}
delete cd;//释放临时空间
}//CreatHuffmanCode
哈夫曼编码的解码
-
构造哈夫曼树
-
依次读入二进制码
-
读入0则走向左孩子;读入1则走向右孩子
-
一旦到达某叶子时,即可译出字符
-
然后再从根出发继续译码,直到结束
算法这里不去介绍了,结合上面,以及先前章节容易实现