目录
一.概述
本系统主要功能主要有三:
1.可将哈夫曼树的构建过程清楚地展现出来;
2.可通过哈夫曼树的成功构建得到哈夫曼编码;
3.可将哈夫曼树的树形结构清楚地展现出来;
此处将权值序列{8 5 29 7 8 14 23 3 11}构建成哈夫曼树;
输入各节点权值:
先通过权值排序得到初态:
开始构建哈夫曼树:
此处省略若干步直接到完成状态...
哈夫曼编码打印:
树形结构打印:
哈夫曼树结构:
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就是层数。
有了层数还需要注意,结点与结点之间是不是同一层,所以需要设计一个结构,里面放着对应结点的层数,告知在第几层,以及它的横向坐标,有了这两个量就可以开始打印了。
这里每个结点在第几层比较容易得到,只需要从根节点开始往下层序遍历,然后逐层加一即可;
难点在于计算结点的横坐标,通过多次尝试得出:
左结点的横坐标=它父亲结点横坐标-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位数的时候就开始看出瑕疵了。如果有更好的方案欢迎交流。。。