霍夫曼算法
对于给出的一组权w={5,6,8,12},通过霍夫曼算法求出的扩充二叉树的带权外部路径为——?
1.有关霍夫曼树的相关概念 霍夫曼树:指所有叶子结点的二叉树中带权路径长度最小的二叉树。
节点的带权路径长度:从树的根节点到该节点的路径长度与该节点权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和。
(1)根据给定的n个权值{w1,w2,...,wn}构造n棵二叉树的集合F={T1,T2,...,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树均空。
(2)在F中选取两棵根结点的权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为其左、右子树上根结点的权值之和。
(3)在F中删除这两棵树,同时将新得到的二叉树加入F中。
(4)重复(2)和(3),直到F中只含一棵树为止。这棵树便是最优二叉树。
简单点说, 路径求法是这样的.先从这组权值中选取最小的两个结点如5和6组成新树,父结点W=11,将11加入权值中并去掉5和6,w={11,8,12},然后又选取最小的两个结点11和8,组成新树,父结点值为19加入权值中并去掉11和8,w={19,12}.直到最后根结点W=31.
这个时候将所有叶子结点和它的路径长度相乘再进行累加
所以是5*3+6*3+8*2+12*1 = 61
#include
<string.h>
#include
"global.h"
#define
MAX
10000;
typedef
struct
{
unsigned
int
weight;
unsigned
int
parent,lchild,rchild;
}
HTNode
,*
HuffmanTree
;
// 动态分配数组存储赫夫曼树
typedef
char
**
HuffmanCode
;
//动态分配数组存储赫夫曼编码表
void
Select(
HuffmanTree
HT,
int
i,
int
&s1,
int
&s2);
void
rootstartHuffmancode(
HuffmanTree
&HT,
HuffmanCode
&HC,
int
n);
void
leafstartHuffmancode(
HuffmanTree
&HT,
HuffmanCode
&HC,
int
n);
void
Huffmancoding(
HuffmanTree
&
HT
,
HuffmanCode
&
HC
,
int
*
w
,
int
n
)
//w存放n个字符的权值,构造赫夫曼树HT,并求出n个字符的赫夫曼编码HC
{
if
(
n
<=1)
return
;
int
i,s1,s2;
int
m=
n
*2-1;
HT
=(
HuffmanTree
)malloc((m+1)*
sizeof
(
HTNode
));
HT
[0].weight=
MAX
;
//s1,s2的初值设为0,此处为Select函数做铺垫
for
( i=1;i<=
n
;i++)
//HT前n个分量表示叶子结点,最后一个分量表示根基点
{
//0号单元未用,初始化权值
HT
[i].lchild=
NULL
;
HT
[i].parent=
NULL
;
HT
[i].rchild=
NULL
;
HT
[i].weight=*
w
++;
}
for
(i=
n
+1;i<=m;i++)
//初始化暂时未用空间
{
HT
[i].lchild=
NULL
;
HT
[i].parent=
NULL
;
HT
[i].rchild=
NULL
;
}
for
(i=
n
+1;i<=m;i++)
{
//在HT[1..i-1]选择parent为0且weight最小的两个结点,序号为s1和s2。
Select(
HT
,i-1,s1,s2);
HT
[s1].parent=i;
HT
[s2].parent=i;
if
(s1<s2)
//这里的if是为了同样的深度,序号小的为左结点,仅仅是为了和书上一致,无意义。
{
HT
[i].lchild=s1;
HT
[i].rchild=s2;}
else
{
HT
[i].lchild=s2;
HT
[i].rchild=s1;}
HT
[i].weight=
HT
[s1].weight+
HT
[s2].weight;
}
rootstartHuffmancode(
HT
,
HC
,
n
);
//leafstartHuffmancode(HT,HC,n);
}
//------- 从叶子到根逆向求每个字符的赫夫曼编码---------//
void
leafstartHuffmancode(
HuffmanTree
&
HT
,
HuffmanCode
&
HC
,
int
n
)
{
HC
=(
HuffmanCode
)malloc((
n
+1)*
sizeof
(
char
*));
//分配n个字符编码的头指针向量
char
*cd=(
char
*)malloc(
n
*
sizeof
(
char
));
//分配求编码的工作空间
cd[
n
-1]=
'\0'
;
//编码结束符
int
p,start,c;
for
(
int
i=1;i<=
n
;i++)
//逐个求赫夫曼编码
{
start=
n
-1;
//编码结束符位置
c=i;
p=
HT
[i].parent;
//从叶子到根逆向求编码
while
(p)
{
if
(
HT
[p].lchild==c)
cd[--start]=
'0'
;
else
cd[--start]=
'1'
;
c=p;
p=
HT
[p].parent;
}
//编码是从start到n-2,共n-start-1个字符,第n-1个位置为‘\0’,总共需要n-start。
HC
[i]=(
char
*)malloc(
sizeof
(
char
)*(
n
-start));
//strcpy(char* dest, const char *src);把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间
strcpy(
HC
[i],&cd[start]);
}
free(cd);
}
//---从根出发遍历整棵树,求各个叶子结点所表示的字符------//
//--无栈飞递归遍历霍夫曼树,求霍夫曼编码!!!!!!!!!!!挺巧妙的算法!!
void
rootstartHuffmancode(
HuffmanTree
&
HT
,
HuffmanCode
&
HC
,
int
n
)
{
char
*cd=(
char
*)malloc(
n
*
sizeof
(
char
));
//分配求编码的工作空间
HC
=(
HuffmanCode
)malloc((
n
+1)*
sizeof
(
char
*));
//分配n个字符编码的头指针向量
int
m=2*
n
-1;
int
l,i,j=0;
for
(i=1;i<=m;i++)
HT
[i].weight=0;
//遍历赫夫曼树时用作结点的标志
l=m;
while
(l)
{
if
(
HT
[l].weight==0)
//向左
{
HT
[l].weight=1;
if
(
HT
[l].lchild==
NULL
&&
HT
[l].rchild==
NULL
)
//登记叶子结点的编码
{
HC
[l]=(
char
*)malloc((j+1)*
sizeof
(
char
));
cd[j]=
'\0'
;
strcpy(
HC
[l],cd);
}
else
{
l=
HT
[l].lchild; cd[j++]=
'0'
;
}
}
else
if
(
HT
[l].weight==1)
//向右
{
HT
[l].weight=2;
if
(
HT
[l].rchild!=0)
{
l=
HT
[l].rchild; cd[j++]=
'1'
;
}
}
else
//左右都走过了,返回
{
HT
[l].weight=0; l=
HT
[l].parent; --j;
//退回父亲结点,编码长度减1
}
}
free(cd);
}
void
Select(
HuffmanTree
HT
,
int
i
,
int
&
s1
,
int
&
s2
)
//在HT[1..i]选择parent为0且weight最小的两个结点,序号为s1和s2。
{
s1
=
s2
=0;
for
(
int
j=1;j<=
i
;j++)
{
if
(
HT
[j].parent==
NULL
)
{
if
(
HT
[
s1
].weight>
HT
[j].weight)
{
int
temp=
s1
;
//s1总比s2小,如果这里不将s1赋值给s2会出错。
s1
=j;
s2
=temp;
}
else
if
(
HT
[
s2
].weight>
HT
[j].weight)
s2
=j;
}
}
}
main.cpp:
//-------------Huffman TEST-------------------//
int
w[100]={5,29,7,8,14,23,3,11};
//测试用例P148
HuffmanTree
HT;
HuffmanCode
HC;
Huffmancoding(HT,HC,w,8);
for
(
int
i=1;i<=8;i++)
printf(
"%s\n"
,*(HC+i));