数据结构实训作业——哈夫曼树(c语言)

目录

一.概述

二.构建哈夫曼树  

三.哈夫曼编码 

四.打印树形结构 

五.完整代码


一.概述

本系统主要功能主要有三:

1.可将哈夫曼树的构建过程清楚地展现出来;

2.可通过哈夫曼树的成功构建得到哈夫曼编码;

3.可将哈夫曼树的树形结构清楚地展现出来;

数据结构实训作业——哈夫曼树(c语言)

 

此处将权值序列{8 5 29 7 8 14 23 3 11}构建成哈夫曼树;

输入各节点权值:

数据结构实训作业——哈夫曼树(c语言)

 先通过权值排序得到初态:

数据结构实训作业——哈夫曼树(c语言) 

开始构建哈夫曼树: 

 数据结构实训作业——哈夫曼树(c语言)

此处省略若干步直接到完成状态... 

 数据结构实训作业——哈夫曼树(c语言)

哈夫曼编码打印: 

数据结构实训作业——哈夫曼树(c语言)  

树形结构打印: 

数据结构实训作业——哈夫曼树(c语言)

 

 

哈夫曼树结构:

typedef struct Htree{
    int data;
    int parent,lchild,rchild;
    int weight;
}Htree;
typedef struct{
	Htree TData[MAXSIZE];//哈夫曼树 
	int size;//这是该树的大小,n*2 
}HFMtree;

 由于打印树形结构需要还设计了树节点队列(先进先出)

struct node{
	//结点队列 
	Htree Tree[MAXSIZE];
	int layer[MAXSIZE];//所在层 
	int locate[MAXSIZE];//所在位置 
	int head;
	int tail;	
};

 由于算法需要还设计了树结点堆栈(先进后出)

typedef struct{
	Htree Array[MAXSIZE];
	int top;//堆栈头 
}Stack_Array;//顺序表堆栈 

二.构建哈夫曼树  

构建哈夫曼树算法:

之前有特地写过关于构建哈夫曼树的一篇 。

主要是在构建过程中展现出来,所以要在那之中加入打印函数:

void printQ(HFMtree *hmt,int i1,int i2,int i3){
	//打印过程状态 
	int size=hmt->size;
	int i;
	printf("-----------------------------------------------\n");
	printf("\t结点\tweight\tparent\tlchild\trchild\n");
	for(i=0;i<size-1;i++){
		if(i+1==i1||i+1==i2)printf("-->");//作为被选中的标识
		if(i+1==i3)printf("----->>");//作为新结点标识
		printf("\t  %d\t  %d\t  %d\t   %d\t   %d\n",i+1,hmt->TData[i+1].weight,hmt->TData[i+1].parent,hmt->TData[i+1].lchild,hmt->TData[i+1].rchild);
	}
	printf("-----------------------------------------------\n");
}

本次的构建函数:

//创建哈夫曼树
void Create(int n,int weight[],HFMtree &HT){
	 int i,j;			
	//n是结点数,weight是权重的数组
	 printf("哈夫曼树的构造过程如下:\n");
	 Qsort(0,n-1,weight);//对权重数组进行从小到大排序
	 printf("通过将权值集合进行升序排序,得到以下HT初态:\n");
	 HT.size=n*2;
	 Stack_Array S;
	 //初始化权重数组,加入哈夫曼数组
	 for(i=1;i<=n;i++){
	 	HT.TData[i].data=i;
	 	HT.TData[i].weight=weight[i-1];
	 	HT.TData[i].parent=HT.TData[i].lchild=HT.TData[i].rchild=0;//初始化为0 
	 }
	 printS(&HT);
	 system("pause");
	 for(i=n;i>=1;i--)PushStack_Array(&S,HT.TData[i]);//从大到小入栈
	 i=n+1;
	 
	 //此时栈顶为最小的两个
	 while(!isEmpty(S)){//循环至堆栈为空 	 	
		 //先出栈两次,得到两个权值最小的结点 
		 Htree h1,h2,ht;
		 h1=PopStack_Array(&S);
		 h2=PopStack_Array(&S);
		 
		 ht.weight=h1.weight+h2.weight;
		 ht.lchild=h1.data;ht.rchild=h2.data;//设置左右儿子
		 ht.data=i;
		 ht.parent=0;
		 HT.TData[i++]=ht;//放入哈夫曼树        
		 HT.TData[h1.data].parent=HT.TData[h2.data].parent=ht.data;//把原结点的父亲更新一下 
		 	 
		 if(ht.data!=HT.size)printf("选择结点:\t%d\t%d\n加入结点:\t%d\n",h1.data,h2.data,ht.data);
		 printQ(&HT,h1.data,h2.data,ht.data);//打印过程 
		 //printT(&HT);//打印树形结构 
		 system("pause");
		 if(ht.data!=HT.size-1)
		 PushStack_Array(&S,ht);//将新结点入栈 
		 sortS(&S);//对堆栈从大到小排序(栈顶最小) 
	 } 
	 printf("-----------------------------------------\n");
	 printf("------------哈夫曼树构建完成!------------\n");
	 printf("-----------------------------------------\n");
	 
}

三.哈夫曼编码 

 打印哈夫曼编码:

由结点往上追溯到它父亲直到根节点,是左子树编码为0,右子树则编码为1;

最后得到该字符串数组是逆序的,所以要逆序打印出来。

void printP(HFMtree *hmt,int size){
	//打印哈夫曼编码 
	printf("-----------------------------------------------\n");
	printf("%d个字符的哈夫曼编码如下:\n",size);
	int i,j;
	for(i=1;i<=size;i++){
		int cnt=0;
		char *hfmCode;
		hfmCode=(char*)malloc(sizeof(char)*size);
		int r=i;
		int p=0;
		while(p!=2*size-1){	
		p=hmt->TData[r].parent;	
		if(hmt->TData[p].lchild==r){
			hfmCode[cnt++]='0';
		}
		else if(hmt->TData[p].rchild==r){
			hfmCode[cnt++]='1';
		}
		r=p;
		
		}
		printf("权值为<% 3d>的哈夫曼编码为:",hmt->TData[i].weight);			
		for(j=cnt-1;j>=0;j--){
			printf("%c",hfmCode[j]);
		}
		printf("\n");
		free(hfmCode);
		if(cnt>Nlayer)Nlayer=cnt+1;
	}
	printf("-----------------------------------------------\n");
}

四.打印树形结构 

打印树形结构:

我们需要得到二叉树的层数,层数在上面求编码的时候已经得到计算。

Nlayer就是层数。 

数据结构实训作业——哈夫曼树(c语言)

有了层数还需要注意,结点与结点之间是不是同一层,所以需要设计一个结构,里面放着对应结点的层数,告知在第几层,以及它的横向坐标,有了这两个量就可以开始打印了。

这里每个结点在第几层比较容易得到,只需要从根节点开始往下层序遍历,然后逐层加一即可; 

难点在于计算结点的横坐标,通过多次尝试得出:

左结点的横坐标=它父亲结点横坐标-2^(总层数-当前结点层数-1);

然后在倒数一两层的时候比较特殊了,可能会得到奇怪的结果

如最后一层:2^(6-6-1)=1/2,2,倒数第二层(6-5-1)=1

这样会导致对不齐,所以需要手动修改。(都是2公倍数,所以这里加2)

if(ret<=2)ret+=2;//会有极端的情况出现,所以需要微调

通过一轮层序遍历,我们将结点的层次,坐标都以及摸清楚了,接下来就需要打印了 。

在打印的时候,分为两种情况:

1.横坐标多大就打印多少个空格,然后在打印相应的数值(权值);

这一步是每次换层的时候就需要打印它的坐标数的空格。

2.当前结点非左边第一个的时候,只需要打印本结点坐标数减去左边结点的坐标数空格即可;

做到这一步,还有一个要注意的问题,我们还需要考虑打印下去的权值数字占多大,为了统一计算,我使用了%2d这样的格式进行打印。这样就可以进行统一了。如果某节点左边有k个结点的话,就需要少打印k个空格。(这一步也是慢慢调试出来的)

结点队列结构:

struct node{
	//结点队列 
	Htree Tree[MAXSIZE];//结点数组
	int layer[MAXSIZE];//所在层 
	int locate[MAXSIZE];//所在位置(坐标)
	int head;
	int tail;	
};

 完整树形打印函数: 

void printT(HFMtree *ht){
	//打印树形结构 
	printf("-----------------------------------------------\n");
	printf("---------------哈夫曼树形结构如下--------------\n");
	struct node q;
	q.head=q.tail=0;
	//通过根结点往下遍历
	Htree r=ht->TData[ht->size-1];
	
	q.Tree[q.tail]=r;//根进队
	q.layer[q.tail]=1;//初始化为第1层 
	q.locate[q.tail]=20;//把根结点放20个空格的位置 
	q.tail++;
	int i=0,j;
	while(q.head!=q.tail){
		Htree t=q.Tree[q.head];//出队 		
		if(t.lchild!=0){
			q.Tree[q.tail]=ht->TData[t.lchild];//左边有孩子就进队 
			q.layer[q.tail]=q.layer[q.head]+1;//更新层数
			int ret=pow(2,Nlayer-q.layer[q.tail]-1);//2^(总层数-1)
			if(ret<=2)ret+=2;//会有极端的情况出现,所以需要微调
			q.locate[q.tail]=q.locate[q.head]-ret;//减去相应位置 
			q.tail++;
		} 
		if(t.rchild!=0){
			q.Tree[q.tail]=ht->TData[t.rchild];//右边有孩子就进队 
			q.layer[q.tail]=q.layer[q.head]+1;//更新层数 
			int ret=pow(2,Nlayer-q.layer[q.tail]-1);
			if(ret<=2)ret+=2;
			q.locate[q.tail]=q.locate[q.head]+ret;//加上相应 
			q.tail++;
		}
		q.head++;
	}
    //以下是打印部分
	int floor=1,k=0;
	for(i=0;i<q.tail;i++){
		int lo=q.locate[i];
		if(i==0){			
			for(j=0;j<lo;j++){
				printf(" ");
			}
		}
		else if(floor!=q.layer[i]){
			//到下一层就该换行 
			printf("\n");
			printf("\n");
			floor=q.layer[i];			
			for(j=0;j<lo;j++){
				printf(" ");
			}
			k=1;				
		}
		else{
			if(lo-q.locate[i-1]>0){
				for(j=0;j<lo-q.locate[i-1]-k;j++){
				printf(" ");
				}
			}
			k+=1;
		}
		printf("%2d",q.Tree[i].weight);	
	}
	printf("\n-----------------------------------------------\n");
}

五.完整代码

#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#include<conio.h>
#define MAXSIZE 1000
/*
哈夫曼树的构建——版本1.0 无字符版  
以下为测试数据 
8 5 29 7 8 14 23 3 11
7 9 11 5 7 8 2 3
7 13 7 8 3 29 6 1
4 7 5 2 4
*/
int Nlayer=0;
typedef struct Htree{
    int data;
    int parent,lchild,rchild;
    int weight;
}Htree;
struct node{
	//结点队列 
	Htree Tree[MAXSIZE];
	int layer[MAXSIZE];//所在层 
	int locate[MAXSIZE];//所在位置 
	int head;
	int tail;	
};
typedef struct{
	Htree TData[MAXSIZE];//哈夫曼树 
	int size;//这是该树的大小,n*2 
}HFMtree;
typedef struct{
	Htree Array[MAXSIZE];
	int top;//堆栈头 
}Stack_Array;//顺序表堆栈 
void Qsort(int left,int right,int a[]){
	if(left>=right){//递归出口 
		return;
	}
	int temp=a[left];//做好标兵 
	int i=left,j=right;
	while(i<j){
		//从右边开始检查起 
		while(temp<a[j]&&j>i){
			j--;
		}
		if(temp>=a[j]&&j>i){
			int t=a[j];
			a[j]=a[i];
			a[i]=t;
			i++;
		}
		while(temp>a[i]&&j>i){
			i++;
		}
		if(temp<a[i]&&j>i){
			int t=a[i];
			a[i]=a[j];
			a[j]=t;
			j--;
		}
	}
	//出来的时候i=j
	a[j]=temp;//在这里左边比他小 右边比他大
	Qsort(left,i-1,a);
	Qsort(i+1,right,a); 
}
void PushStack_Array(Stack_Array *S,Htree value){
	//顺序堆栈入栈
	S->Array[++S->top]=value;
}
Htree PopStack_Array(Stack_Array *S){
	Htree x=S->Array[S->top];
	S->top--;
	return x;
}
int isEmpty(Stack_Array S){
	if(S.top<=-1)return 1;
	return 0;
}
void sortS(Stack_Array *S){
	//对堆栈进行从大到小排序 (权重)
	int i,j,len=S->top+1;
	for(i=1;i<len;i++){
		for(j=1;j<i;j++){
			if(S->Array[j].weight<S->Array[i].weight){
				Htree t=S->Array[j];
				S->Array[j]=S->Array[i];
				S->Array[i]=t;
			}
		}
	}
}
void printS(HFMtree *hmt){
	//打印初始状态 
	int size=hmt->size;
	int i;
	printf("-----------------------------------------------\n");
	printf("\t结点\tweight\tparent\tlchild\trchild\n");
	for(i=0;i<size-1;i++){
		printf("\t  %d\t  %d\t  %d\t   %d\t   %d\n",i+1,hmt->TData[i+1].weight,hmt->TData[i+1].parent,hmt->TData[i+1].lchild,hmt->TData[i+1].rchild);
	}
	printf("-----------------------------------------------\n");
}
void printQ(HFMtree *hmt,int i1,int i2,int i3){
	//打印过程状态 
	int size=hmt->size;
	int i;
	printf("-----------------------------------------------\n");
	printf("\t结点\tweight\tparent\tlchild\trchild\n");
	for(i=0;i<size-1;i++){
		if(i+1==i1||i+1==i2)printf("-->");
		if(i+1==i3)printf("----->>");
		printf("\t  %d\t  %d\t  %d\t   %d\t   %d\n",i+1,hmt->TData[i+1].weight,hmt->TData[i+1].parent,hmt->TData[i+1].lchild,hmt->TData[i+1].rchild);
	}
	printf("-----------------------------------------------\n");
}
void printP(HFMtree *hmt,int size){
	//打印哈夫曼编码 
	printf("-----------------------------------------------\n");
	printf("%d个字符的哈夫曼编码如下:\n",size);
	int i,j;
	for(i=1;i<=size;i++){
		int cnt=0;
		char *hfmCode;
		hfmCode=(char*)malloc(sizeof(char)*size);
		int r=i;
		int p=0;
		while(p!=2*size-1){	
		p=hmt->TData[r].parent;	
		if(hmt->TData[p].lchild==r){
			hfmCode[cnt++]='0';
		}
		else if(hmt->TData[p].rchild==r){
			hfmCode[cnt++]='1';
		}
		r=p;
		
		}
		printf("权值为<% 3d>的哈夫曼编码为:",hmt->TData[i].weight);			
		for(j=cnt-1;j>=0;j--){
			printf("%c",hfmCode[j]);
		}
		printf("\n");
		free(hfmCode);
		if(cnt>Nlayer)Nlayer=cnt+1;
	}
	printf("-----------------------------------------------\n");
}
void printT(HFMtree *ht){
	//打印树形结构 
	printf("-----------------------------------------------\n");
	printf("---------------哈夫曼树形结构如下--------------\n");
	struct node q;
	q.head=q.tail=0;
	//通过根结点往下遍历
	Htree r=ht->TData[ht->size-1];
	
	q.Tree[q.tail]=r;//根进队
	q.layer[q.tail]=1;//初始化为第1层 
	q.locate[q.tail]=20;//20个空格的位置 
	q.tail++;
	int i=0,j;
	while(q.head!=q.tail){
		Htree t=q.Tree[q.head];//出队 		
		if(t.lchild!=0){
			q.Tree[q.tail]=ht->TData[t.lchild];//左边有孩子就进队 
			q.layer[q.tail]=q.layer[q.head]+1;//更新层数
			int ret=pow(2,Nlayer-q.layer[q.tail]-1);
			if(ret<=2)ret+=2;
			q.locate[q.tail]=q.locate[q.head]-ret;//减去1个空格的位置 
			q.tail++;
		} 
		if(t.rchild!=0){
			q.Tree[q.tail]=ht->TData[t.rchild];//右边有孩子就进队 
			q.layer[q.tail]=q.layer[q.head]+1;//更新层数 
			int ret=pow(2,Nlayer-q.layer[q.tail]-1);
			if(ret<=2)ret+=2;
			q.locate[q.tail]=q.locate[q.head]+ret;//加上1个空格的位置 
			q.tail++;
		}
		q.head++;
	}
	int floor=1,k=0;
	for(i=0;i<q.tail;i++){
		int lo=q.locate[i];
		if(i==0){			
			for(j=0;j<lo;j++){
				printf(" ");
			}
		}
		else if(floor!=q.layer[i]){
			//到下一层就该换行 
			printf("\n");
			printf("\n");
			floor=q.layer[i];			
			for(j=0;j<lo;j++){
				printf(" ");
			}
			k=1;				
		}
		else{
			if(lo-q.locate[i-1]>0){
				for(j=0;j<lo-q.locate[i-1]-k;j++){
				printf(" ");
				}
			}
			k+=1;
		}
		printf("%2d",q.Tree[i].weight);	
	}
	printf("\n-----------------------------------------------\n");
}
//创建哈夫曼树
void Create(int n,int weight[],HFMtree &HT){
	 int i,j;			
	//n是结点数,weight是权重的数组
	 printf("哈夫曼树的构造过程如下:\n");
	 Qsort(0,n-1,weight);//对权重数组进行从小到大排序
	 printf("通过将权值集合进行升序排序,得到以下HT初态:\n");
	 HT.size=n*2;
	 Stack_Array S;
	 //初始化权重数组,加入哈夫曼数组
	 for(i=1;i<=n;i++){
	 	HT.TData[i].data=i;
	 	HT.TData[i].weight=weight[i-1];
	 	HT.TData[i].parent=HT.TData[i].lchild=HT.TData[i].rchild=0;//初始化为0 
	 }
	 printS(&HT);
	 system("pause");
	 for(i=n;i>=1;i--)PushStack_Array(&S,HT.TData[i]);//从大到小入栈
	 i=n+1;
	 
	 //此时栈顶为最小的两个
	 while(!isEmpty(S)){//循环至堆栈为空 	 	
		 //先出栈两次,得到两个权值最小的结点 
		 Htree h1,h2,ht;
		 h1=PopStack_Array(&S);
		 h2=PopStack_Array(&S);
		 
		 ht.weight=h1.weight+h2.weight;
		 ht.lchild=h1.data;ht.rchild=h2.data;//设置左右儿子
		 ht.data=i;
		 ht.parent=0;
		 HT.TData[i++]=ht;//放入哈夫曼树        
		 HT.TData[h1.data].parent=HT.TData[h2.data].parent=ht.data;//把原结点的父亲更新一下 
		 	 
		 if(ht.data!=HT.size)printf("选择结点:\t%d\t%d\n加入结点:\t%d\n",h1.data,h2.data,ht.data);
		 printQ(&HT,h1.data,h2.data,ht.data);//打印过程 
		 //printT(&HT);//打印树形结构 
		 system("pause");
		 if(ht.data!=HT.size-1)
		 PushStack_Array(&S,ht);//将新结点入栈 
		 sortS(&S);//对堆栈从大到小排序(栈顶最小) 
	 } 
	 printf("-----------------------------------------\n");
	 printf("------------哈夫曼树构建完成!------------\n");
	 printf("-----------------------------------------\n");
	 
}
int main(){
	int W[MAXSIZE];
	HFMtree HT;
	int cnt=0,i;
	printf("-----------------------------------------------\n");
	printf("---------------哈夫曼树的构建流程--------------\n");
	printf("-----------------------------------------------\n");
	printf("请输入需要哈夫曼编码的字符个数:");
	scanf("%d",&cnt);
	printf("\n");
	for(i=0;i<cnt;i++){
		printf("请输入第 %d 字符的权值:",i+1);
		scanf("%d",&W[i]);
		printf("\n");
	}
	Create(cnt,W,HT);//构建过程 
	printf("按任意键查看哈夫曼编码.......\n");
	getch();
	printP(&HT,cnt);//打印编码 
	printf("按任意键查看哈夫曼树形结构...\n");
	getch();
	printT(&HT);//打印树形结构 
 	return 0;
}

 其他测试案例:

可以看出在数值为2位数的时候就开始看出瑕疵了。如果有更好的方案欢迎交流。。。

数据结构实训作业——哈夫曼树(c语言)

 数据结构实训作业——哈夫曼树(c语言)

 数据结构实训作业——哈夫曼树(c语言)

 

上一篇:[ Redis14篇]字典之渐进式Hash结构


下一篇:C#--Hashtable键值对集合