哈夫曼树(最优二叉树)基本知识点
1、基本术语
①路径:从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。
②结点的路径长度:两结点间路径上的分支数。
③树的路径长度:从树根到每一个结点的路径长度之和。记作:TL。
上述的0是根结点到自身的长度。
注意:
结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树。但路径长度最短的不一定是完全二叉树。
④权(weight):将树中结点赋给一个有着某种含义的值,则这个数值称为该结点的权。
⑤结点的带权路径长度:从根结点到该结点之间的路径长度与该结点的权的乘积。
⑥树的带权路径长度:树中所有叶子结点的带权路径长度之和。
⑦哈夫曼树(最优树):带权路径长度(WPL)最短的树。
注:
“带权路径长度最短”是在“度相同”的树中比较而得的结果,因此有最优二叉树、最优三叉树之称等等。
⑧满二叉树不一定是哈夫曼树;
具有相同带权结点的哈夫曼树不唯一;
哈夫曼树中权越大的叶子离根越近。
⑨贪心算法:构造哈夫曼树时首先选择权值小的叶子结点。
2、哈夫曼树的构造算法
(1)哈夫曼算法(构造哈夫曼树的方法)
-
构造森林全是根
根据n个给定的权值{w1,w2,……,wn}构成n棵二叉树的森林F={T1,T2,……,Tn},其中Ti只有一个带权为wi的根结点。 -
选用两小造新树
在F中选取两棵根结点的权值最小的树作为左右子树,构造一棵新的二叉树,且设置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。 -
删除两小添新人
重复上面两步,直到森林中只有一棵树为止,这棵树即为哈夫曼树。
(2)哈夫曼算法口诀:①构造森林全是根;②选用两小造新树;③删除两小添新人;④重复2、3剩单根。
(3)例1:
包含n个叶子结点的哈夫曼树中共有2n-1个结点。哈夫曼树的结点的度数为0或2,没有度为1的结点。
(4)例二:
注意:上述步骤中第二步中最小的是5、5两个根,不是5、6!
(5)总结
①在哈夫曼算法中,初始时有n棵二叉树,要经过n-1次合并最终形成哈夫曼树。
②经过n-1次合并产生n-1个新结点,且这n-1个新结点,且这n-1个新结点都是具有两个孩子的分支结点。
可见:
哈夫曼树中共有n+n-1=2n-1个结点,且其所有的分支结点的度均不为1。
3、哈夫曼树构造算法的实现
(1)采用顺序存储结构——一维结构数组
结点类型定义
typedef struct{
int weight;//权值
int parent,lch,rch;
}HTNode,*HuffmanTree;
哈夫曼树中共有2n-1个结点不使用0下标,数组大小为2n。
(2)案例引入
例如,有n=8,权值为W={7,19,2,6,32,3,21,10}构造哈夫曼树具体过程图示如下:
找权值最小的两个根要在双亲为零的结点中找。(删除两小即将两个小的结点的双亲赋值)。结束条件:parent列表只剩下一个数为0时结束。
(2)算法实现
①步骤:
- 初始化HT[1…2n-1]:lch=rch=parent=0;
- 输入初始n个叶子结点:置HT[1…n]的weight值。
- 进行以下n-1次合并依次产生n-1个结点HT[i],i=n+1…2n-1:
4、哈夫曼树的构造(具体代码)
//寻找最小的两个权值
void Select(HuffmanTree& HT, int n, int& s1, int& s2)
{
//寻找第一个双亲域为0且权值最小的结点
int min = 0;
//找到第一个双亲域为0的,下标暂存到min
for (int i = 1; i <= n; ++i)
{
if (HT[i].parent == 0) {
min = i;
break;
}
}
for (int i = 1; i <= n; i++)
if (HT[i].parent == 0 && HT[min].weight > HT[i].weight)
min = i;
s1 = min;
for (int i = 1; i <= n; ++i)
{
if (HT[i].parent == 0 && i != s1) {
min = i;
break;
}
}
for (int i = 1; i <= n; i++)
if (HT[i].parent == 0 && HT[min].weight > HT[i].weight && i != s1)
min = i;
s2 = min;
}
void CreatHuffmanTree(HuffmanTree HT,int n){
//构造哈夫曼树——哈夫曼算法
if(n<=1) return;
m=2*n-1;//数组共2n-1个元素
HT=new HTNode[m+1];//0号单元未用,HT[m]表示根结点
for(i=1;i<=m;++i){
//将2n-1个元素的lch、rch、parent置为0
HT[i].lch=0;HT[i].rch=0;HT[i].parent=0;
}
for(i=1;i<=n;++i)
cin>>HT[i].weight;//输入前n个元素的weight值
//初始化结束接下来建立哈夫曼树
for(i=n+1;i<=m;i++)
{
Select(HT,i-1,s1,s2);//在HT[k](1<=k<=i-1)中选择两个其双亲域为0,且权值最小的结点,并返回它们在HT中序号s1、s2
HT[s1].parent=i;HT[s2].parent=i;
HT[i].lch=s1;HT[i].rch=s2;
HT[i].weight=HT[s1].weight+HT[s2].weight;
//i的权值为左右孩子权值之和
}
}