C数据结构:哈夫曼树算法实现与应用

带权二叉树

引入路径相关知识

  • 结点之间的路径长度:到达两结点之间的路径分支数就是路径长度。

  • 树的路径长度:根结点到每一个结点之间的路径长度之和。

      解释:因为说的是树,包含整一棵树的,
      从根结点到每一孩子结点的路径长度加起来的总和成为 “树” 的路径长度。
      对后面解释哈夫曼树非常有用。
    

权值的解释

  • 例子:好比从广州到成都,两个结点之间所需要的车费就是权值
  • 通常给出的是去往下一结点的权值,也就是结点的权值
  • A->B,一般会给出B对应的权值,B的权值就是A到B的路径权值
    @@我们接下去要特别研究的哈夫曼树其实就是带权的二叉树@@

认识WPL

WPL(带权路径长度)= 节点全值 * 路径长度之和

  • 说简单点就是二叉树的带权路径长度之和就是WPL。
    (根结点到每一个结点的带权路径之和就是WPL)

最优二叉树

我在构造哈夫曼树的时候意识到如果不按照规定的方法构造二叉树的话就会造成很多种可能,构造出的二叉树也不尽相同,所以我们的前辈哈夫曼博士就想了一个办法就是构造最优二叉树,最优二叉树就是我们要学习的哈夫曼树吗。

  • 最优二叉树就是在众多解法中找到WPL最小那一棵二叉树,这一棵就叫做哈夫曼树。

构造哈夫曼树的过程

我对哈夫曼树如何建立的操作的理解

  • 对于给出n个带权结点,这里指的是结点,这些结点暂时还没有连通,每一个结点独立开,把这些结点称为根结点,他们是一个集合。
  • 其次我们在这些根结点权值中找到最小的两个结点,然后用一个全新的根结点将其做成二叉树。注意,我们默认把最小的放在左边,次小的(比左边大的)放在右边。
  • 该新二叉树的左右两个权值加起来就变成了新的二叉树的根结点权值,把该新根结点放进剩下的结点中作为一个集合。
  • 继续回到集合中,找两个最小的权值进行重复操作。
    如此一来当根结点只剩下一个的时候就是生成了一个哈夫曼树了。

可以看下图作为一个例子:
生成哈夫曼树
总结哈夫曼树生成过程产生的结点规律:n -> n-1
其实能隐隐约约感受到新生成结点的数量规律,每找到两个权值最小的结点就会生成一个新根结点加进集合中,假设原先有n个根结点在集合中,那规律就是生成一次新根,就会减少一个根结点,那么最后就会生成n-1个新的,注意是新的结点,不是n-1个根结点,我们最后是只剩下一个二叉树根结点。

  • 最后二叉树中所有的结点数:n + n - 1 = 2n - 1 ,就是最终的总结点数。

哈夫曼树的应用

最优二叉树优点

  • 最优二叉树有一个厉害的点是能够保证每一个叶子结点所经过的路径是不相同的。
  • 我们通过这个路径不同的对其进行应用,就可以对应到编码和解码。因为我们能保证每一个在叶子结点的信息都有唯一对应的编码形式。
  • 编码容易解码难,因为我们有时候拿的不是自己编的码,需要结别人的编码时候就会显得异常困难。

其实哈夫曼树的精髓就在于构造,不断生成新结点把两小权值连在一起。 这里需要和二叉搜索树进行区分开,因为哈夫曼树针对于叶子结点的,二叉搜索树是每一个结点都有他对应的信息,会用于增删查改。
这里我们就先对哈夫曼树进行深入了解,会了哈夫曼树,二叉搜索树学起来就很简单。

建立哈夫曼树

明确三个任务

  • ①先对给出的n根结点的信息复制到一个2n-1的二维数组当中
  • ②在2n数组中且在没有父母结点元素中找到最小的那两个元素进行合并,这里的合并并不指真正把他合并,只是把父母结点修改成同一个父母结点就行。
  • ③然后就是把两个结点的权值加起来,作为新结点也就是这两个结点的父结点的权值,再把找到的这两个的父母结点修改成同一个。
  • 注意,我们找到的父母结点插入位置是在 n + 1 的这个位置开始,然后每找到一个就往下加入,直到插完插到2n-1位置就代表该哈夫曼树建立成功。
  • ④在我们寻找最小值的时候那个for循环切记不能越界或者不是当前结点数量。
  • ⑤结点数量在变化,我们每次寻找最小结点的时候那个条件判断也要跟着变化,所以这时候结构体中要时刻记录着结点数量,我们可以用结点数量作为循环条件。
    注意的细节:
  • 建立二叉树的时候一定要注意对数组下标不要越界。
  • 叶子结点是最终二叉树所有的结点的两倍减一倍数。
  • 其次在寻找两个最小结点的时候要找没有父母结点的,也就是还没有组成一对的根结点。
  • 组成的新的根结点是加入到n+1的数组下标中,凡是新结点都要接入到数组中。
  • 因此寻找最小两个结点的时候也要在新加入的父母结点中找,简单来说就是找最小值的时候一直找没有父母结点的就行,直到剩下最后一个作文二叉树根结点就不需要找了,根结点也没有父母结点。

代码如下:

PS:因为鄙人为了方便自己记录,直接浪费两个数组中第一个元素,都将其设置为空或者不管。因此我们计算的时候就不用绞尽脑汁的去想还要加一的事情了。所以我一般是直接新建2n个空间的数组,然后慢慢存。

结构体代码部分

typedef struct _hafuman{
	int wight;
	int parent;
	int lchild;
	int rchild;
}HF;


typedef struct _HF_Tree{
	HF hafuman[17];
	int hflength;
	int leaves; 
}HF_Tree; 

HF_Tree Tree; 

建立操作代码

这里的Find_Minindex(Tree)函数是寻找Tree中最小的一个元素,且返回该元素的下标,找到后要立马把该最小结点的父母结点进行修改,修改后才能继续用,想要找两个就用两次就好了。

void Make_hafuman(HF_Tree* Tree)
 {
 	int i = Tree->leaves+1;
 	int j, index = 0, lch, rch;
 	while( i < Tree->leaves*2)
 	{
 		lch = Find_Minindex(Tree);
		Tree->hafuman[lch].parent = i;
		Tree->hafuman[i].lchild = lch;
	
		rch = Find_Minindex(Tree);
		Tree->hafuman[rch].parent = i;
		Tree->hafuman[i].rchild = rch;
		
		Tree->hafuman[i].wight = (Tree->hafuman[lch].wight + Tree->hafuman[rch].wight);
		i++;
		Tree->hflength++;
	 }
 }

找到最小结点(※难点)

int Find_Minindex(HF_Tree* Tree)
{
 	int length = sizeof(Tree->hafuman)/sizeof(HF);
 	//printf("暂停测试:%d",length);
 	int i, min = 1000000, index = 0;
 	
 	for(i = 1; i <= Tree->hflength; i++)
 	{
 		if(Tree->hafuman[i].parent == 0)
 		{
 			if(Tree->hafuman[i].wight < min)
			 {
			 	min = Tree->hafuman[i].wight;
			 	index = i;
			  } 
		 }
	 }
	 return index;
 }

附上建立哈夫曼树源代码

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>

typedef struct _hafuman{
	int wight;
	int parent;
	int lchild;
	int rchild;
}HF;


typedef struct _HF_Tree{
	HF hafuman[17];
	int hflength;
	int leaves; 
}HF_Tree; 

HF_Tree Tree; 

void Make_hafuman(HF_Tree*);

int Find_Minindex(HF_Tree*);


//生成哈夫曼树 
int main()
{
	/*
	给出 n 个元素数组作为根节点权值
	生成一个2n 空间数组,先把这个权值赋值进去 
	结构体数组:权值域,父母域,左孩子,右孩子(左小右大)
	 
	生成哈夫曼树函数参数:数组地址, 
	*/ 
	
	int w[9] = {0, 7, 19, 2, 6, 32, 3, 21, 10}; 
 	int i,sum =0;
 	for(i = 1; i < sizeof(w)/sizeof(int); i++)
 	{
 		Tree.hafuman[i].wight = w[i]; 
 		Tree.hafuman[i].lchild = 0;
 		Tree.hafuman[i].parent = 0;
 		Tree.hafuman[i].rchild = 0;
 		Tree.leaves++;
 		sum+=Tree.hafuman[i].wight;
	 }
	 Tree.hflength = Tree.leaves;
	 printf("%d,总和:%d\n",Tree.leaves, sum);
	Make_hafuman(&Tree);
	 
	for(i = 1; i <= Tree.hflength; i++)
	{
		printf("%d:%d,%d,%d,%d\n",i, Tree.hafuman[i].wight, Tree.hafuman[i].parent, Tree.hafuman[i].lchild,Tree.hafuman[i].rchild);
	}
	
	return 0;
 } 
 
 void Make_hafuman(HF_Tree* Tree)
 {
 	int i = Tree->leaves+1;
 	int j, index = 0, lch, rch;
 	while( i < Tree->leaves*2)
 	{
 		lch = Find_Minindex(Tree);
		Tree->hafuman[lch].parent = i;
		Tree->hafuman[i].lchild = lch;
	
		rch = Find_Minindex(Tree);
		Tree->hafuman[rch].parent = i;
		Tree->hafuman[i].rchild = rch;
		
		Tree->hafuman[i].wight = (Tree->hafuman[lch].wight + Tree->hafuman[rch].wight);
		i++;
		Tree->hflength++;
	 }
 }
 
int Find_Minindex(HF_Tree* Tree)
{
 	int length = sizeof(Tree->hafuman)/sizeof(HF);
 	//printf("暂停测试:%d",length);
 	int i, min = 1000000, index = 0;
 	
 	for(i = 1; i <= Tree->hflength; i++)
 	{
 		if(Tree->hafuman[i].parent == 0)
 		{
 			if(Tree->hafuman[i].wight < min)
			 {
			 	min = Tree->hafuman[i].wight;
			 	index = i;
			  } 
		 }
	 }
	 return index;
 }

收获:让我对二叉搜索树和哈夫曼树有了真正意义上的区分和了解
二叉搜索树是倾向于每一个结点都有其对应信息,是用来存储信息,增删查改。
哈夫曼树是用于解码编码,而且只是研究根结点到叶子结点的权值。
共同点:都需要找到其最优二叉树。一般是左孩子小,右孩子大。

posted @ 2022-07-09 00:10  竹等寒  阅读(100)  评论(0编辑  收藏  举报  来源