基于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;
} 

 

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个基于数组的哈夫曼树权值实现的示例代码,供您参考: ```c #include <stdio.h> #define MAX_NODE_NUM 1000 // 哈夫曼树节点结构体 typedef struct { int weight; // 权值 int parent; // 父节点 int leftChild; // 左孩子 int rightChild; // 右孩子 } HuffmanNode; // 构建哈夫曼树 void createHuffmanTree(HuffmanNode huffmanTree[], int weight[], int n) { int i, j, k; int min1, min2; // 初始化哈夫曼树 for (i = 0; i < 2 * n - 1; i++) { huffmanTree[i].parent = -1; huffmanTree[i].leftChild = -1; huffmanTree[i].rightChild = -1; } // 设置叶子节点的权值 for (i = 0; i < n; i++) { huffmanTree[i].weight = weight[i]; } // 构建哈夫曼树 for (i = 0; i < n - 1; i++) { min1 = min2 = MAX_NODE_NUM; k = 0; // 查找权值最小的两个节点 for (j = 0; j < n + i; j++) { if (huffmanTree[j].parent == -1 && huffmanTree[j].weight < min1) { min2 = min1; min1 = huffmanTree[j].weight; k = j; } else if (huffmanTree[j].parent == -1 && huffmanTree[j].weight < min2) { min2 = huffmanTree[j].weight; } } // 设置父节点 huffmanTree[k].parent = n + i; huffmanTree[n + i].weight = min1 + min2; // 设置左孩子和右孩子 for (j = 0; j < n + i; j++) { if (huffmanTree[j].parent == -1) { if (huffmanTree[j].weight == min1) { huffmanTree[j].parent = n + i; huffmanTree[n + i].leftChild = j; } else if (huffmanTree[j].weight == min2) { huffmanTree[j].parent = n + i; huffmanTree[n + i].rightChild = j; } } } } } int main() { int i; int weight[] = {5, 6, 8, 7, 15}; int n = sizeof(weight) / sizeof(weight[0]); HuffmanNode huffmanTree[MAX_NODE_NUM]; // 构建哈夫曼树 createHuffmanTree(huffmanTree, weight, n); // 输出哈夫曼树的权值 printf("Huffman Tree Weight:\n"); for (i = 0; i < 2 * n - 1; i++) { printf("%d ", huffmanTree[i].weight); } printf("\n"); return 0; } ``` 在这个代码中,`HuffmanNode` 结构体表示哈夫曼树的节点,包含权值、父节点、左孩子和右孩子等信息。`createHuffmanTree` 函数用于构建哈夫曼树,接收一个 `HuffmanNode` 数组 `huffmanTree`,一个整型数组 `weight` 表示每个叶子节点的权值,以及一个整数 `n` 表示叶子节点的个数。 在构建哈夫曼树的过程中,我们首先初始化哈夫曼树的节点信息,然后将叶子节点的权值设置到节点中。接着,我们进行 `n-1` 次迭代,在每一次迭代中查找权值最小的两个节点,并将它们合并为一个新的节点。最后,我们就可以得到一个完整的哈夫曼树。 在示例代码中,我们使用了一个数组来表示哈夫曼树,节点的下标就是节点的编号。在构建哈夫曼树的过程中,我们通过设置节点的 `parent`、`leftChild` 和 `rightChild` 等属性来表示节点之间的关系。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值