概念介绍:
树的路径长度:从树根到每一个结点的路径长度之和,而路径长度是指路径上分支数目。
树的带权路径长度(WPL):树中所有叶子结点的带权路径长度之和。结点的带权路径长度为从该结点到树根之间的路径长度与结点上权的乘积。
最优二叉树:是指WPL最小的二叉树,又称赫夫曼树。
构造赫夫曼树的过程:
(1)根据给定的n个权重{w0, w2, ..., wn-1}构造n棵二叉树的集合F={T1, T2, T3, ..., Tn-1},其中每棵二叉树Ti只有一个带权为wi的根结点,其左右子树均为空;
(2)从F中选取两棵根结点的权重最小的树作为左右子树构造一棵新的二叉树,并让新的二叉树的根结点的权重为其左右子树上根结点的权重之和;
(3)从F中删除上述两棵树,并将新的二叉树加入F中;
(4)重复(2)和(3),直到F只有一棵树为止。这棵树就是赫夫曼树。
注意:这样形成的赫夫曼树并不一定是唯一的,因为在形成新的二叉树时,没有对其左右子树的根结点的权重大小进行要求。
实现:
程序如下:
// 权重或出现的概率*100
var weight = [5, 29, 7, 8, 14, 23, 3, 11];
// 赫夫曼编码主函数
function huffman(){
var HTArray = initHT();
var total = weight.length * 2 - 1;
// 填充赫夫曼树数组的后n-1项,即循环n-1次
// 每次从赫夫曼树数组中找出权重最小且parent属性为0的两个节点形成新的二叉树
for(var i = weight.length; i < total; i++){
var children = select_unsorted(HTArray);
HTArray[i].weight = HTArray[children.lchild].weight + HTArray[children.rchild].weight;
HTArray[i].lchild = children.lchild;
HTArray[i].rchild = children.rchild;
HTArray[children.lchild].parent = i;
HTArray[children.rchild].parent = i;
// HTArray = sort(HTArray);
}
return HTArray;
}
// 根据权重数组初始化赫夫曼树数组
// 形成HT数组
// 数组中每一项是一个对象,对象包括weight、parent、lchild、rchild4个属性
// 赫夫曼数组总共有2*n-1个项,即赫夫曼树的总节点数,n为叶子节点的个数
function initHT(){
// 赫夫曼树数组
var HT = [];
var total = weight.length * 2 - 1;
for(var i = 0; i < weight.length; i++){
HT.push({
weight: weight[i],
parent: 0,
lchild: 0,
rchild: 0
});
}
for( ; i < total; i++){
HT.push({
weight: NaN,
parent: 0,
lchild: 0,
rchild: 0
});
}
// return sort(HT);
return HT;
}
// 选择权重最小且parent属性为0的两个节点
// array为赫夫曼树数组,未排序
// 返回值为一个包含lchild和rchild两个属性的对象,lchild/rchild表示第一/二小且parent属性为0的节点的索引
function select_unsorted(array){
var min = Number.MAX_VALUE,
secondMin = Number.MAX_VALUE;
var children = {};
// 选择权重第一小的节点
for(var i = 0; i < array.length; i++){
if(isNaN(array[i].weight)){
break;
}
if(array[i].parent == 0 && array[i].weight < min){
min = array[i].weight;
children.lchild = i;
}
}
// 选择权重第二小的节点
for(i = 0; i < array.length; i++){
if(isNaN(array[i].weight)){
break;
}
if(array[i].parent == 0 && array[i].weight < secondMin && i != children.lchild){
secondMin = array[i].weight;
children.rchild = i;
}
}
return children;
}
// 验证
var finalHT = huffman();
给定的权重数组是[5, 29, 7,8, 14, 23, 3, 11]
根据上述权重数组形成的赫夫曼树数组的初始状态如下:
最终的赫夫曼树数组状态:
对应的赫夫曼树为:
赫夫曼树译码:
假设从根结点开始,左子树有0,右子树为1.
算法的思想是:
(1)从每个叶子结点开始,记录child为叶子结点,parent为叶子结点的父结点,并记这个叶子结点的编码为空字符串;
(2)判断是parent的左子树与child相等还是右子树与child相等。如果是左子树与child相等,则给字符串拼接上“0”,反之,则拼接上“1”;
(3)将parent赋值给child,parent再取child的父结点;
(4)重复(2)(3)直到parent为0为止;
(5)将字符串翻转。
程序如下:
// 形成二进制编码
// array是最终的赫夫曼树数组且没有经过排序
// 因未排序,所以前n(权重值的个数)个项就是所以得叶子节点
function decodeHuffman(array){
debugger;
var HC = [];
// 从叶子节点开始,层层地向根节点解码
for(var i = 0; i < weight.length; i++){
var temp = '';
var child = i;
var parent = array[i].parent;
while(parent != 0){
if(array[parent].lchild == child){
temp = temp + '0';
} else {
temp = temp + '1';
}
child = parent;
parent = array[parent].parent;
}
// temp.split("").reverse().join("")的作用是翻转字符串
HC.push(temp.split("").reverse().join(""));
}
return HC;
}
// 验证
var HC = decodeHuffman(finalHT);
最后, [5, 29, 7, 8, 14, 23, 3, 11]对应的结果是