带权路径最小的二叉树称为哈弗曼树。哈弗曼树是一棵二叉树。
由于哈弗曼树中没有度为1的结点,所以有n个叶子结点的哈弗曼树有2n-1个结点,可以存储在大小为2n-1的一维数组中。每个结点要包含结点的权重、双亲结点的信息、孩子结点的信息。
wight | parent | lchild | rchild |
哈弗曼树的结点形式
哈夫曼树的存储结构定义如下:
typedef struct{
int weight;//结点的权重
int parent, lchild, rchild;//结点的双亲,左孩子,右孩子下标
}HTNode, *HuffmanTree;
哈夫曼树的构造算法及实现:
#include<stdio.h>
#include<stack>
#define MAXSIZE 100
using namespace std;
typedef struct{
int weight;//结点的权重
int parent, lchild, rchild;//结点的双亲,左孩子,右孩子下标
}HTNode, *HuffmanTree;
//当前森林中选择双亲为0且权值最小的两个树根结点s1,s2;
void SelectNode(HuffmanTree &HT, int t, int &s1, int &s2){
int mins1=HT[t].weight;
s1=t;
int mins2=HT[t].weight;
s2=t;
for(int k=1;k<=t;k++){
if(HT[k].weight<mins1 && HT[k].parent==0){
mins1=HT[k].weight;
s1=k;
}
}
//HT[s1].parent=1;
for(int k=1;k<=t;k++){
if(HT[k].weight<mins2 && k!=s1 && HT[k].parent==0){
mins2=HT[k].weight;
s2=k;
}
}
}
/*
哈弗曼树的构造步骤:
1.首先动态申请2n个存储单元,然后循环2n-1次,从1号单元开始,一次将1至2n-1所有单元中的
双亲、左孩子、右孩子的下标都初始化为0;最后循环n此,输入前n个单元中叶子结点的权重值;
2.循环n-1次,通过n-1次的选择、删除与合并来创建哈弗曼树。
选择是从当前森林中选择双亲为0且权值最小的两个树根结点s1,s2;
删除是指将s1,s2的双亲改为非0;
合并就是将s1,s2的权值之和作为新结点的权值一次存入数组的第n+1之后的单元中,同时记录这个
新结点左孩子的下标为s1,右孩子的下标为s2。
*/
void CreateHuffmanTree(HuffmanTree &HT, int n){
//构造哈弗曼树HT
if(n<1){
return;
}
int m=2*n-1;
//这里我们不使用数组的0号位,所以需要分配m+1个单元
HT=new HTNode[m+1];
for(int i=1;i<=m;i++){
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
for(int i=1;i<=n;i++){
printf("请输入第%d个叶子结点的权重:",i);
scanf("%d", &HT[i].weight);
}
for(int i=n+1; i<=m; i++){
//当前森林中选择双亲为0且权值最小的两个树根结点s1,s2
int s1, s2;
//在 HT[k] (1<=k<=i-1) 中选择两个其双亲域为 0 且权值最小的结点,并返回它们在 HT中的序号 s1 和 s2
SelectNode(HT, i-1, s1, s2);
//从森林中删除s1和s2,将s1和s2的结点的双亲置为i
HT[s1].parent=i;
HT[s2].parent=i;
//s1和s2分别作为HT[i]的左孩子和右孩子
HT[i].lchild=s1;
HT[i].rchild=s2;
//i的权值为s1和s2权值之和
HT[i].weight=HT[s1].weight+HT[s2].weight;
}
}
int main(){
HuffmanTree HT;
printf("请输入哈夫曼树叶子结点数:");
int n;
scanf("%d", &n);
CreateHuffmanTree(HT, n);
for(int i=1;i<=2*n;i++){
printf("哈夫曼树各个结点的下标%d和权重%d:\n", i, HT[i].weight);
}
}