目录
写在前面
现在时间是2021年11月25日下午2:24,在花费一晚上加两节自习加中午没睡觉,Joe树皮终于实现了她的HuffmanTree,中间N次调试发现错误的时候都让我很是怀疑,我是周树皮,不是周木头,错得我离谱,改得我吐血。
无语,快上课了,文章先开个头,后续会更新HuffmanTree的实现原理,顺便分享一下我的心酸调试过程(已鸽)。
发现我已经语无伦次了,管他管他我说的就是病句
555555想睡觉觉!!!!!!!!
1.术前准备
1.1 saysay原理
Huffman Tree的创建原理,很简单,一句话就是:从当前所有节点中选出权重最小与权重次小的两个结点将它们的合并为新结点,新结点的权重为两个结点权重之和,并用新结点替换最小与次小结点,重复进行此过程,直到只剩下一个结点;我们惊奇得发现:结点两两融合形成新结点并被替换,最终新结点数目为初始结点数-1,故总结点数目为2*初始结点数-1。(数学知识,自己推去)
hhhhhhhh好长啊,看我一句话,胜似一段话SOS
阿巴阿巴阿巴总结下挽回尊严?
纵观全程,我们要做的其实就是:
挑选->融合->替换,直到只剩下一个结点!!!!
(棒!(我觉得)精辟!)
1.2 手工操作Huffman Graph
接下来请读者跟着俺一起来play play 介个Huffman Graph
1)初始化整表结构
-
我们假设初始结点数目(NodeNum)为3,并规定结点数目为下标(index),每个结点需记录:权重(weight)、父结点(parent)、左孩子结点(lchild)、右孩子结点(rchild)这五个信息作为表头。
-
根据公式可得:新创建结点数为2,总结点数(TotalNode)为5,即index最大为5。
-
当前所有结点未被赋值,初始化时所有数据全部置零。
操作结束,我们创建了如下这张Huffman Graph:
2)初始结点权重赋值
依次将3个初始结点的权重(weight)赋值为1、2、3
我们便可得到如下Huffman Graph:
3)挑选所有节点中权重(weight)最小与次小的结点
新结点操作:
- 权重(weight)更新为最小结点与次小结点权重之和
- 左孩子(lchild)更新为最小结点
- 右孩子(rchild)更新为次小结点
次小最小结点操作:
- 父结点(parent)更新为新结点
更新后Huffman Graph如下图:
4.重复第3步操作直到更新完index=5结点(TotalNode)的各项数据。
Huffman Graph 生成!
2.上机实操带你写个Bug
2.1 存储结构设计
远看山有色,近听水无声
横向看生成的Huffman Graph,将index栏看成对应下标的结点,会发现每个结点都存储着相同的四种信息:权重(weight)、父结点(parent)、左孩子结点(lchild)、右孩子结点(rchild)。
于是手工操作的Huffman Graph在计算机看来就等效成图二的形态,显而易见,其存储结构就是一个结构体数组。而之后所有的操作,其实就是基于结构体数组各存储单元的操作。
这里,我们将结构体的一个存储单元提取出来,此操作对应在Huffman Graph上就是取出对应行。
到这里,我相信读者应该能很容易得定义出单个结点(HuffNode)的存储结构了吧!
typedef struct Node{
int weight;//权重
int parent;//父结点
int lchild;//左孩子
int rchild;//右孩子
}HuffNode;
为了程序的可读性,Joe树皮模仿了栈的存储结构封装了一个Huffman Graph的存储结构,该栈包含一个数组下标(index)以及一个用于存储HuffmanNode类型结点的结构体数组(Tree[MAXSIZE])
// Huffman树数组结构
typedef struct Tarry{
HuffNode Tree[MAXSIZE]; //用于存储结点的数组
int index; //用于数组下标
}HuffTree;
在所有操作开始前,我们需在main函数中定义一个HuffTree类型的指针变量(HFTree)并将它初始化:1)动态分配内存空间 2)初始化下标(index)。
之后我们便可以用 HFTree->Tree[]表示结构体数组,用HFTree->index表示数组下标,HFTree->Tree[HFTree->index]便可以表示对应结点,再用HFTree->Tree[HFTree->index].x(x为weight、lchild、rchild、parent)表示该结点存储的对应数据了。
2.2 排序选择
2.2.1 变量声明
int min1,min2,x1,x2,i,flag=0;
//min1、min2 分别记录最小值,次小值
//x1、x2 分别记录最小值下标,次小值下标
//i 用于内层循环下标,表示此轮比较选择i次
//flag记录每轮新结点的下标
2.2.1 可恶的下标
先来分析i(此轮选择次数)、NodeNum(初始结点数)、TotalNode(总结点数)之间的关系。
前面我们已经明确:TotalNode=2*NodeNum-1
下图也能清楚看出初始化状态时二者的关系,
接着,我们将过程展开,并将公式变形为:TotalNode=NodeNum+NodeNum’-1
令:
TotalNode表示当前总结点数(初始结点+当前新生成结点)
NodeNum表示初始结点数
NodeNum’表示当前初始结点数(初始结点数+当前生成结点数)
NodeNum’-1则表示新生成结点数
到这里,我们会发现,整个重复的选择比较操作存在两个变量:当前总结点数,当前初始结点数。因此我们需要用一个双层for循环进行解决。
从前面的分析可知,当前总结点数受当前新生成结点的控制,因此,当前新生成结点数(NodeNum-1)应为外层循环;内层循环内,我们需要遍历当前初始结点(NodeNum’)以找到最小与次小结点,直到更新完所有结点的weight。
需明确!内层循环我们实现的是当前初始结点的比较,因此,for执行次数为当前总结点数-1,清楚认识这点在之后代码实现中定义for循环终止条件设定时十分重要!
用伪代码表示即:
for(index=第一个结点;index=第一个结点:当前新生成结点,index++){
for(i=第一个结点;i=第一个结点:当前总结点;i++){
比较选择
}
}
代码实现如下:
for((*HFTree)->index=1; (*HFTree)->index<NodeNum;(*HFTree)->index++)
{
for(i=1;i<NodeNum+(*HFTree)->index;i++){
挑选比较
}
}
2.2.2 小孩子才做选择,次小最小我全都要
首先,在每轮内层循环开始前,我们需将min1、min2初始为MaxValue,该操作使循环执行后min1、min2一定会被重新赋值;将x1、x2初始为0,防止对新生成结点下标产生影响。
在内层循环中,我们对每个还未融合(无父结点)结点的权重进行如下比较:
1)Tree[i].weight<min1
- 原最小权重成为次小权重 min2=min1
- 更新最小权重 min1=Tree[i].weight
- 原最小下标成为次小下标
- 更新最小下标
2)min1<Tree[i].weight<min2
- 更新次小权重 min2=Tree[i].weight
- 更新最小下标 x2=i
3)Tree[i].weight>min2
无需改动
代码实现如下
//挑选次小最小构建新结点
int min1,min2,x1,x2,flag=0; //规定min1记录最小值
int i; // 用于内层循环下标
//初始化min1 min2都为最大值(防止第一个权重最小最后 两个结点一样
for((*HFTree)->index=1; (*HFTree)->index<NodeNum;(*HFTree)->index++){
min1=min2=MaxValue;
x1=x2=0;
//新创建结点下标为NodeNum+当前index
printf("index=%d\n",(*HFTree)->index);
for(i=1;i<NodeNum+(*HFTree)->index;i++){
printf("i=%d\n",i);
//(1)如果当前权重小于最小权重 则更新最小 原最小成为次小
//已使用的结点跳过
printf("0.x1=%d x1=%d\n",x1,x2);
if((*HFTree)->Tree[i].parent==0&&(*HFTree)->Tree[i].weight<min1){
//原最小成为次小
min2=min1;
x2=x1;
//更新最小
min1=(*HFTree)->Tree[i].weight;
x1=i;
printf("1.x1=%d x2=%d\n",x1,x2);
printf("1.min1=%d min2=%d \n",min1,min2);
}
//当前权重大于最小小于次小 记录为次小
else
if((*HFTree)->Tree[i].parent==0&&(*HFTree)->Tree[i].weight<min2){
min2=(*HFTree)->Tree[i].weight;
x2=i;
printf("2.x1=%d x2=%d",x1,x2);
printf("1.min1=%d min2=%d \n",min1,min2);
}
//新结点左孩子
}
2.3 纯纯的数组操作
2.3.1 新结点赋值还不是有手就行
- 记录当前创建结点下标 flag=Tree->index+NodeNum
- 新结点权重=最小结点权重+次小结点权重
- 新节点左孩子为最小结点下标
- 新节点右孩子为次小结点下标
- 最小结点父节点为新节点
- 次小结点父节点为新节点
代码实现如下:
flag=(*HFTree)->index+NodeNum;//当前创建结点下标
//挑选结点的父结点记录
(*HFTree)->Tree[x1].parent=flag;
(*HFTree)->Tree[x2].parent=flag;
printf("flag=%d\n",flag);
printf("x1=%d,x2=%d\n",x1,x2);
2.3.2打印Huffman Graph 还不是有键盘就行
循环初始条件: 从第一个结点开始(Tree->index=1)
循环终止条件: 到总结点数结束(Tree->index=TotalNode)
循环执行操作:输出index对应结点的各个数据
加点细节\t \n 表头,搞定输出!
void PrintHuffmanTree(HuffTree *HFTree,int TotalNode)
{
HuffNode *PNode=(HuffNode*)malloc(sizeof(HuffNode)); //用于指向当前操作结点
//输出表头
printf("\t\tHuffman Graph\t\t\n");
printf("index\t weight\t parent\t lchild\t rchlid\t\n ");
printf("\n");
for(HFTree->index=1;HFTree->index<=TotalNode;HFTree->index++){
PNode=&(HFTree->Tree[HFTree->index]); //PNode指向当前操作数组单元
printf(" %d\t %d\t %d\t %d\t %d\t\n ",HFTree->index,PNode->weight,PNode->parent,PNode->lchild,PNode->rchild);
printf("\n");
}
}
3.De个Bug
鸽了hh
4.附录
4.1完整代码
#include<stdio.h>
#include<malloc.h>
#define MAXSIZE 50
#define MaxValue 1000
//Huffman结点结构
typedef struct Node{
int weight;//权重
int parent;//父结点
int lchild;//左孩子
int rchild;//右孩子
}HuffNode;
// Huffman树数组结构
typedef struct Tarry{
HuffNode Tree[MAXSIZE]; //用于存储结点的数组
int index; //用于数组下标
}HuffTree;
//Huffman树的初始化
//定义从数组第二个元素开始存储 即数组下标为1开始
void HuffmanTreeInit(HuffTree **HFTree,int TotalNode)
{
(*HFTree)=(HuffTree*)malloc((TotalNode+1)*sizeof(HuffNode));//分配存储空间
(*HFTree)->index=-1; //初始化下标
}
void CreatHuffmanTree(HuffTree **HFTree,int NodeNum,int TotalNode)
{
HuffNode *PNode=(HuffNode*)malloc(sizeof(HuffNode)); //用于指向当前操作结点
//初始化赋值
printf("请依次输入%d个结点的权值:\n",NodeNum);
for((*HFTree)->index=1; (*HFTree)->index<=TotalNode;(*HFTree)->index++){
PNode=&(*HFTree)->Tree[(*HFTree)->index]; //PNode指向当前操作数组单元 记得&!!!!!
//初始结点权重赋值
if((*HFTree)->index<=NodeNum){
scanf("%d",&PNode->weight);
}
else{
PNode->weight=0;
}
//左孩子、右孩子、父节点赋值
PNode->parent=0;
PNode->lchild=0;
PNode->rchild=0;
}
//挑选次小最小构建新结点
int min1,min2,x1,x2,flag=0; //规定min1记录最小值
int i; // 用于内层循环下标
//初始化min1 min2都为最大值 防止第一个权重最小导致最后两个结点一样都为第一个
//里层for循环结束需要重新赋值min啊救命!!!!
for((*HFTree)->index=1; (*HFTree)->index<NodeNum;(*HFTree)->index++){
min1=min2=MaxValue;
x1=x2=0;
//新创建结点下标为NodeNum+当前index
for(i=1;i<NodeNum+(*HFTree)->index;i++){
//(1)如果当前权重小于最小权重 则更新最小 原最小成为次小
//已使用的结点跳过
if((*HFTree)->Tree[i].parent==0&&(*HFTree)->Tree[i].weight<min1){
//原最小成为次小
min2=min1;
x2=x1;
//更新最小
min1=(*HFTree)->Tree[i].weight;
x1=i;
}
//(2)当前权重大于最小小于次小 记录为次小
else
if((*HFTree)->Tree[i].parent==0&&(*HFTree)->Tree[i].weight<min2){
min2=(*HFTree)->Tree[i].weight;
x2=i;
}
//新结点左孩子
}
flag=(*HFTree)->index+NodeNum;//当前创建结点下标
//挑选结点的父结点记录
(*HFTree)->Tree[x1].parent=flag;
(*HFTree)->Tree[x2].parent=flag;
//新结点创建
(*HFTree)->Tree[flag].weight=(*HFTree)->Tree[x1].weight+(*HFTree)->Tree[x2].weight;//新结点权重
(*HFTree)->Tree[flag].lchild=x1; //新结点左孩子
(*HFTree)->Tree[flag].rchild=x2;
}
printf("\n");
}
void PrintHuffmanTree(HuffTree *HFTree,int TotalNode)
{
HuffNode *PNode=(HuffNode*)malloc(sizeof(HuffNode)); //用于指向当前操作结点
//输出表头
printf("\t\tHuffman Graph\t\t\n");
printf("index\t weight\t parent\t lchild\t rchlid\t\n ");
printf("\n");
for(HFTree->index=1;HFTree->index<=TotalNode;HFTree->index++){
PNode=&(HFTree->Tree[HFTree->index]); //PNode指向当前操作数组单元
printf(" %d\t %d\t %d\t %d\t %d\t\n ",HFTree->index,PNode->weight,PNode->parent,PNode->lchild,PNode->rchild);
printf("\n");
}
}
int main()
{
HuffTree *HFTree; //
int NodeNum; //用于记录初始创建结点个数
int TotalNode; //用于记录创建后总结点个数
printf("请输入创建结点个数:\n");
scanf("%d",&NodeNum);
TotalNode=2*NodeNum-1;
HuffmanTreeInit(&HFTree,TotalNode) ;
CreatHuffmanTree(&HFTree,NodeNum,TotalNode);
PrintHuffmanTree(HFTree,TotalNode);
return 0;
}
4.2测试结果
终于实现了!!!!
4.3 总结与展望
无报错,可运行,但是无法输出预期结果,自行查询百度无法解决,询问老师同学无果?