数据结构---哈夫曼树
## 哈夫曼树及其应用
名称 | 概念 |
---|---|
哈夫曼树 | 又称最优树,是一类带权路径长度最短的树 |
路径 | 从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径 |
路径长度 | 路径上的分支数目称作路径长度 |
树的路径长度 | 从树根到每一结点的路径长度之和 |
权 | 赋予某个实体的一个量,是对实体的某个或某些属性的数值化描述 |
结点的带权路径长度 | 从该结点到树根之间的路径长度与结点上权的乘积 |
树的带权路径长度 | 树中所有叶子结点的带权路径长度之和 |
在结点数目相同的二叉树中,完全二叉树是路径长度最短的二叉树
满二叉树不一定是最优二叉树
哈夫曼树中权越大的叶子距离根越近,这也是符合哈夫曼树的特点,带权路径最短
哈夫曼树的构造算法
1.构造森林全是根
2.选用两小造新树
3.删除两小添新人
4.重复2,3添新根
解释:将所有结点看作是一片森林,森林每棵树都只有一个结点;选权值最小的两个根结点构成新树(权值为两者之和);在原来的森林删去已经构成新树的结点,把新树加进森林;重复进行直到只有一棵树,即为最优树。
哈夫曼算法的实现
采用顺序存储结构,即一维结构数组,数组一共存储2n-1个结点(n个叶子结点,两两相加构成n-1个新结点,一共是2n-1个结点),为了方便查找孩子和双亲,所有每个结点还要包含双亲信息和孩子信息。
结点类型定义
typedef struct
{
int weight;//结点的权值
int parent,lchild,rchild;//结点的双亲、左孩子、右孩子的下标
} HTNode,*HuffmanTree;//动态分配数组存储哈夫曼树
为了方便使用,0号单元不使用,所以数组大小为2n,下标1-n存储叶子结点,n+1-2n-1存储非叶子节点
构造哈夫曼树
void CreateHuffmanTree(HuffmanTree &HT,int n)
{
if(n<=l) return;
m=2*n-l;
HT=new HTNode[m+l];//下标为0未用,所以一共要分配2n个单元,最后一个单元HT[m]表示根结点
for(i=l;i<=m;++i)//将所有单元全部初始化
{
HT[i].parent=0;//每个结点的双亲初始化为零
HT[i].lchild=0;//每个结点的左孩子初始化为零
HT[i].rchild=0;//每个结点的右孩子初始化为零
}
for(i=l;i<=n;++i}//输入前n个叶子结点的权值
cin>>HT[i].weight;
/*- ---- ----- -初始化工作结束, 下面开始创建哈夫曼树- - - - ------ */
for (i=n+l; i<=m; ++i}
{
Select (HT, i-1, sl, s2};//从HT[k] (l=<k=<i-1) 中选择两个其双亲域为0且权值最小的结点,并返回它们在HT中的序号sl和s2
HT[sl].parent=i;HT[s2].parent=i;//得到新结点i, 从森林中删除sl,s2(当有双亲就不再是独立的一棵树,也就不存在于森林中), 将sl和s2的双亲域由0改为i
HT[i].lchild=sl;HT[i].rchild=s2;//sl, s2分别作为i的左右孩子
HT[i].weight=HT[sl].weight+HT[s2].weight;//i的权值为左右孩子权值之和
}
}
哈夫曼编码
为确保对数据文件进行有效的压缩和对压缩文件进行正确的解码, 可以利用哈夫曼树来设计二进制编码。
约定左分支标记为 0, 右分支标记为 1,则根结点到每个叶子结点路径上的 0、1序列即为相应字符的编码。
要设计长度不等的编码,来实现编码长度最短,则任意一个编码都不是其他任何编码的前缀,且哈夫曼树的特点是权越大离根越近,所有将每个字符的概率作为权值,构造哈夫曼树,概率越大的结点,路径越短。
- 哈夫曼编码是前缀编码
没有一片叶子是另一片叶子的祖先,任一哈夫曼码都不会与任意其他 哈夫曼编码的前缀部分完全重叠
- 哈夫曼编码是最优前缀编码
哈夫曼树的带权路径长度最短,编码总长度最短
哈夫曼编码表的存储表示
typedef char **HuffmanCode;// 动态分配数组存储哈夫曼编码表
各字符的哈夫曼编码存储在由HuffmanCode定义的动态分配的数组HC中, 为了实现方便, 数组的0号单元不使用,从1号单元开始使用,所以数组HC的大小为n+1, 即编码表HC包括n+1行。
为了节约空间,动态分配一个长度为n(最坏的情况每两个结点相加,不存在同层次的叶子结点这时长度为n-1,最后一个单元存'\0'来表示结束)的一维数组cd,用来临时存放当前正在求解的第i(i=<1=<n) 个字符的编码
求解编码时是从哈夫曼树的叶子出发, 向上回溯至根结点所以对于每个字符, 得到的编码顺序是从右向左的,故将编码向数组cd存放的顺序也是从后向前的即每个字符的第1个编码存放在cd[n-2]中(cd[n-1]存放字符串结束标志'\0'), 第2个编码存放在cd[n-3]中,依此类推,直到全部编码存放完毕。
根据哈夫曼树求哈夫曼编码
void CreatHuffmanCode(HuffmanTree HT,HuffmanCode &HC,int n)
{
HC=new char* [n+l];//分配存储n个字符编码的编码表空间
cd=new char [n]; //分配临时存放每个字符编码的动态数组空间
cd[n-1]='\0'; //编码结束符
for(i=l;i<=n;++i)
{
start=n-1;//从编码结束符开始
c=i; f=HT [i].parent; //f指向结点c的双亲结点
while(f!=O) //从叶子结点开始向上回溯, 直到根结点
{
-start;//回溯一次start向前指一个位置
if(HT[f] .lchild==c) cd[start]='O';//结点c是f的左孩子, 则生成代码0
else cd[start]='l';//结点c是f的右孩子, 则生成代码1
c=f;f=HT[f] .parent;//继续向上回溯
}//求出第l.个字符的编码
HC[i]=new char[n-start];//为第i个字符编码分配空间
strcpy(HC[i],&cd[start]);//将求得的编码从临时空间cd复制到HC的当前行中
}
delete cd;//释放临时空间
}
|