基于C语言的哈夫曼树的实现(包含完整代码)
一.哈夫曼树的定义:
在许多应用中,树中结点常常被赋予一个表示某种意义的数值,称为该结点的权。从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积,称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度.
在含有n个带权叶结点的二叉树中,其中带权路径长度(WPL)最小的二叉树称为哈夫曼树,也称最优二叉树。
例如,下图的3棵二叉树都有4个叶子结点a,b, c,d,分别带权7,5,2,4, 它们的带权路径长度分别为
(a)WPL = 7*2+5*2+2*2+4*2=36
(b)WPL = 4*2+7*3+5*3+2*1=46
(c)WPL = 7*1+5*2+2*3+4*3=35
(c)树的WPL最小.可以验证,它恰好是哈夫曼树.
二.哈夫曼树的构造:
给定n个权值分别为w1, w2,.., W{,的结点,构造哈夫曼树的算法描述如下:
1)将这n个结点分别作为n棵仅含一个结点的二叉树,构成森林F。
2)构造一个新结点,从F中选取两棵根结点权值最小的树作为新结点的左、右子树,并且
将新结点的权值置为左、右子树上根结点的权值之和。
3)从F中删除刚才选出的两棵树,同时将新得到的树加入F中。
4)重复步骤2)和3),直至F中只剩下一棵树为止。
从上述构造过程中可以看出哈夫曼树具有如下特点:
1)每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大。
2)构造过程中共新建了n-1个结点(双分支结点),因此哈夫曼树的结点总数为2n-1。
3)每次构造都选择2棵树作为新结点的孩子,因此哈夫曼树中不存在度为1的结点。
例如,权值(7,5,2,4)的哈夫曼树的构造过程如下图 :
三.代码实现:
1. 树中结点的结构体
typedef struct TreeNode
{
int weight;//权值
int parent;//父结点
int lchild;//左孩子
int rchild;//右孩子
}TreeNode;
2.哈夫曼树的结构体
typedef struct HFTree
{
TreeNode *data;//数据域
int length;//长度(结点个数)
}HFTree;
3.初始化哈夫曼树
HFTree *initTree(int *weight,int length)
{/*参数一:权值数组
参数二:数组长度*/
HFTree *T = (HFTree *)malloc(sizeof(HFTree));//申请一颗树的空间
T->data = (TreeNode *)malloc(sizeof(TreeNode)*(2*length-1));//申请一个结点的空间,长度由数组长度确定
T->length = length;//长度写入
int i;
for(i=0;i<length;i++)
{/*按序一次写入权值,父结点初始为0,左右孩子初始为-1*/
T->data[i].weight = weight[i];
T->data[i].parent = 0;
T->data[i].rchild = -1;
T->data[i].lchild = -1;
}
return T;
}
4.挑选最小权值的两个结点下标
int *selectMin(HFTree *T)
{
int min = 10000;//第一小
int secondMin = 10000;//第二小
int minIndex;//第一小的下标
int i;
int secondIndex;//第二小的下标
for(i=0;i<T->length;i++)
{/*挨个比对,遇到小的写入min,并把下标写入minIndex*/
if(T->data[i].parent==0)
{
if(T->data[i].weight<min)
{
min = T->data[i].weight;
minIndex = i;
}
}
}
for(i=0;i<T->length;i++)
{/*挨个对比,遇到小的,并且下标不等于第一小的下标,写入seconMin,并把下标写入secondIndex*/
if(T->data[i].parent==0&&i!=minIndex)
{
if(T->data[i].weight<secondMin)
{
secondMin = T->data[i].weight;
secondIndex = i;
}
}
}
int *res = (int *)malloc(sizeof(int)*2);//由于要传两个参数,因此申请大小为2的内存空间
res[0] = minIndex;//写入第一小的下标
res[1] = secondIndex;//写入第二小的下标
return res;//返回指针
}
5.创建哈夫曼树
void creatHFTree(HFTree *T)
{
int *res;//用以接收selectMin()函数的两个参数
int min;//权值第一小的下标
int secondMin;//权值第二小的下标
int i;
int length = T->length*2-1;//叶子结点数目n则总结点数目2n-1
for(i=T->length;i<length;i++)
{
res = selectMin(T);//接收两个最小权值的下标
min = res[0];//第一小赋值给min
secondMin = res[1];//第二小赋值给seconMin
T->data[i].weight = T->data[min].weight+T->data[secondMin].weight;//连个最小的结点相加
T->data[i].lchild = min;//父结点的左孩子下标为min
T->data[i].rchild = secondMin;//父结点的右孩子下标为secondMin
T->data[i].parent = 0;//该父结点的父结点暂时没有,设为0
T->data[min].parent = i;//左孩子的父结点为i
T->data[secondMin].parent = i;//右孩子的父结点为i
T->length++;//树的结点数加一(长度加一)
}
}
6.前序遍历
void preOrder(HFTree *T,int index)
{/*参数一:哈夫曼树的结构体
参数二:树的下标*/
if(index!=-1)//index!=-1表示该树不为空
{
printf("%d ",T->data[index].weight);//输出权值
preOrder(T,T->data[index].lchild);//递归,遍历左子树
preOrder(T,T->data[index].rchild);//递归,遍历右子树
}
}
7.主函数
int main()
{
int weight[4]={1,2,3,4};//定义权值数组
HFTree *T = initTree(weight,4);//初始化哈夫曼树
int *res = selectMin(T);//接收两个最小的权值的下标
printf("%d,%d\n",res[0],res[1]);
creatHFTree(T);//创建哈夫曼树
preOrder(T,T->length-1);//前序遍历哈夫曼树
return 0;
}
完整代码如下:
#include <stdio.h>
#include <stdlib.h>
/*树中结点的结构体*/
typedef struct TreeNode
{
int weight;//权值
int parent;//父结点
int lchild;//左孩子
int rchild;//右孩子
}TreeNode;
/*哈夫曼树的结构体*/
typedef struct HFTree
{
TreeNode *data;//数据域
int length;//长度(结点个数)
}HFTree;
/*初始化哈夫曼树*/
HFTree *initTree(int *weight,int length)
{/*参数一:权值数组
参数二:数组长度*/
HFTree *T = (HFTree *)malloc(sizeof(HFTree));//申请一颗树的空间
T->data = (TreeNode *)malloc(sizeof(TreeNode)*(2*length-1));//申请一个结点的空间,长度由数组长度确定
T->length = length;//长度写入
int i;
for(i=0;i<length;i++)
{/*按序一次写入权值,父结点初始为0,左右孩子初始为-1*/
T->data[i].weight = weight[i];
T->data[i].parent = 0;
T->data[i].rchild = -1;
T->data[i].lchild = -1;
}
return T;
}
/*挑选最小权值的两个结点下标*/
int *selectMin(HFTree *T)
{
int min = 10000;//第一小
int secondMin = 10000;//第二小
int minIndex;//第一小的下标
int i;
int secondIndex;//第二小的下标
for(i=0;i<T->length;i++)
{/*挨个比对,遇到小的写入min,并把下标写入minIndex*/
if(T->data[i].parent==0)
{
if(T->data[i].weight<min)
{
min = T->data[i].weight;
minIndex = i;
}
}
}
for(i=0;i<T->length;i++)
{/*挨个对比,遇到小的,并且下标不等于第一小的下标,写入seconMin,并把下标写入secondIndex*/
if(T->data[i].parent==0&&i!=minIndex)
{
if(T->data[i].weight<secondMin)
{
secondMin = T->data[i].weight;
secondIndex = i;
}
}
}
int *res = (int *)malloc(sizeof(int)*2);//由于要传两个参数,因此申请大小为2的内存空间
res[0] = minIndex;//写入第一小的下标
res[1] = secondIndex;//写入第二小的下标
return res;//返回指针
}
/*创建哈夫曼树*/
void creatHFTree(HFTree *T)
{
int *res;//用以接收selectMin()函数的两个参数
int min;//权值第一小的下标
int secondMin;//权值第二小的下标
int i;
int length = T->length*2-1;//叶子结点数目n则总结点数目2n-1
for(i=T->length;i<length;i++)
{
res = selectMin(T);//接收两个最小权值的下标
min = res[0];//第一小赋值给min
secondMin = res[1];//第二小赋值给seconMin
T->data[i].weight = T->data[min].weight+T->data[secondMin].weight;//连个最小的结点相加
T->data[i].lchild = min;//父结点的左孩子下标为min
T->data[i].rchild = secondMin;//父结点的右孩子下标为secondMin
T->data[i].parent = 0;//该父结点的父结点暂时没有,设为0
T->data[min].parent = i;//左孩子的父结点为i
T->data[secondMin].parent = i;//右孩子的父结点为i
T->length++;//树的结点数加一(长度加一)
}
}
/*前序遍历*/
void preOrder(HFTree *T,int index)
{/*参数一:哈夫曼树的结构体
参数二:树的下标*/
if(index!=-1)//index!=-1表示该树不为空
{
printf("%d ",T->data[index].weight);//输出权值
preOrder(T,T->data[index].lchild);//递归,遍历左子树
preOrder(T,T->data[index].rchild);//递归,遍历右子树
}
}
int main()
{
int weight[4]={1,2,3,4};//定义权值数组
HFTree *T = initTree(weight,4);//初始化哈夫曼树
int *res = selectMin(T);//接收两个最小的权值的下标
printf("%d,%d\n",res[0],res[1]);
creatHFTree(T);//创建哈夫曼树
preOrder(T,T->length-1);//前序遍历哈夫曼树
return 0;
}