C语言实现哈夫曼树、编码、解码及问题总结

姓名:周小蓬 16019110037

转载自:http://blog.csdn.net/F__shigang/article/details/65442550

[嵌牛导读]

Huffman树是一类带权路径长度WPL最短的二叉树,中文名叫哈夫曼树或最优二叉树。

相关概念:

结点的路径长度:从根结点到该结点的路径上分支的数目。

树的路径长度:树中每个结点的路径长度之和。

树的带权路径长度:树中所有叶子结点的带权路径长度之和。

[嵌牛鼻子]

c语言

[嵌牛提问]

如何学习哈夫曼树以及步骤

[嵌牛正文]

构造Huffman树的步骤:

1)  根据给定的n个权值,构造n棵只有一个根结点的二叉树,n个权值分别是这些二叉树根结点的权;

2)  设F是由这n棵二叉树构成的集合,在F中选取两棵根结点权值最小的树作为左、右子树,构造成一颗新的二叉树,置新二叉树根结点的权值等于左、右子树根结点的权值之和。为了使得到的哈夫曼树的结构唯一,规定根结点权值最小的作为新二叉树的左子树。

3)  从F中删除这两棵树,并将新树加入F;

4)  重复2)、3)步,直到F中只含一棵树为止,这棵树便是Huffman树。

说明:n个结点需要进行n-1次合并,每次合并都产生一个新的结点,最终的Huffman树共有2n-1个结点。

2、Huffman编码

Huffman树在通讯编码中的一个应用:

利用哈夫曼树构造一组最优前缀编码。主要用途是实现数据压缩。在某些通讯场合,需将传送的文字转换成由二进制字符组成的字符串进行传输。

方法:

利用哈夫曼树构造一种不等长的二进制编码,并且构造所得的哈夫曼编码是一种最优前缀编码,使所传电文的总长度最短。

不等长编码:即各个字符的编码长度不等(如:0,10,110,011),可以使传送电文的字符串的总长度尽可能地短。对出现频率高的字符采用尽可能短的编码,则传送电文的总长度便尽可能短。

前缀编码:任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。

二、代码实现

使用链表结构构建哈夫曼树并进行编码、解码,代码如下:

[cpp]view plaincopy

#include 

#include 

#include 

typedefintELEMTYPE;

// 哈夫曼树结点结构体

typedefstructHuffmanTree

{

ELEMTYPE weight;

ELEMTYPE id;// id用来主要用以区分权值相同的结点,这里代表了下标

structHuffmanTree* lchild;

structHuffmanTree* rchild;

}HuffmanNode;

// 构建哈夫曼树

HuffmanNode* createHuffmanTree(int* a,intn)

{

inti, j;

HuffmanNode **temp, *hufmTree;

temp = malloc(n*sizeof(HuffmanNode));

for(i=0; i

{

temp[i] = (HuffmanNode*)malloc(sizeof(HuffmanNode));

temp[i]->weight = a[i];

temp[i]->id = i;

temp[i]->lchild = temp[i]->rchild = NULL;

}

for(i=0; i

{

intsmall1=-1, small2;// small1、small2分别作为最小和次小权值的下标

for(j=0; j

{

if(temp[j] != NULL && small1==-1)

{

small1 = j;

continue;

}elseif(temp[j] != NULL)

{

small2 = j;

break;

}

}

for(j=small2; j

{

if(temp[j] != NULL)

{

if(temp[j]->weight < temp[small1]->weight)

{

small2 = small1;

small1 = j;

}elseif(temp[j]->weight < temp[small2]->weight)

{

small2 = j;

}

}

}

hufmTree = (HuffmanNode*)malloc(sizeof(HuffmanNode));

hufmTree->weight = temp[small1]->weight + temp[small2]->weight;

hufmTree->lchild = temp[small1];

hufmTree->rchild = temp[small2];

temp[small1] = hufmTree;

temp[small2] = NULL;

}

free(temp);

returnhufmTree;

}

// 以广义表的形式打印哈夫曼树

voidPrintHuffmanTree(HuffmanNode* hufmTree)

{

if(hufmTree)

{

printf("%d", hufmTree->weight);

if(hufmTree->lchild != NULL || hufmTree->rchild != NULL)

{

printf("(");

PrintHuffmanTree(hufmTree->lchild);

printf(",");

PrintHuffmanTree(hufmTree->rchild);

printf(")");

}

}

}

// 递归进行哈夫曼编码

voidHuffmanCode(HuffmanNode* hufmTree,intdepth)// depth是哈夫曼树的深度

{

staticintcode[10];

if(hufmTree)

{

if(hufmTree->lchild==NULL && hufmTree->rchild==NULL)

{

printf("id为%d权值为%d的叶子结点的哈夫曼编码为 ", hufmTree->id, hufmTree->weight);

inti;

for(i=0; i

{

printf("%d", code[i]);

}

printf("\n");

}else

{

code[depth] = 0;

HuffmanCode(hufmTree->lchild, depth+1);

code[depth] = 1;

HuffmanCode(hufmTree->rchild, depth+1);

}

}

}

// 哈夫曼解码

voidHuffmanDecode(charch[], HuffmanNode* hufmTree,charstring[])// ch是要解码的01串,string是结点对应的字符

{

inti;

intnum[100];

HuffmanNode* tempTree = NULL;

for(i=0; i

{

if(ch[i] =='0')

num[i] = 0;

else

num[i] = 1;

}

if(hufmTree)

{

i = 0;// 计数已解码01串的长度

while(i

{

tempTree = hufmTree;

while(tempTree->lchild!=NULL && tempTree->rchild!=NULL)

{

if(num[i] == 0)

{

tempTree = tempTree->lchild;

}else

{

tempTree = tempTree->rchild;

}

++i;

}

printf("%c", string[tempTree->id]);// 输出解码后对应结点的字符

}

}

}

intmain()

{

inti, n;

printf("请输入叶子结点的个数:\n");

while(1)

{

scanf("%d", &n);

if(n>1)

break;

else

printf("输入错误,请重新输入n值!");

}

int* arr;

arr=(int*)malloc(n*sizeof(ELEMTYPE));

printf("请输入%d个叶子结点的权值:\n", n);

for(i=0; i

{

scanf("%d", &arr[i]);

}

charch[100], string[100];

printf("请连续输入这%d个叶子结点各自所代表的字符:\n", n);

fflush(stdin);// 强行清除缓存中的数据,也就是上面输入权值结束时的回车符

gets(string);

HuffmanNode* hufmTree = NULL;

hufmTree = createHuffmanTree(arr, n);

printf("此哈夫曼树的广义表形式为:\n");

PrintHuffmanTree(hufmTree);

printf("\n各叶子结点的哈夫曼编码为:\n");

HuffmanCode(hufmTree, 0);

printf("要解码吗?请输入编码:\n");

gets(ch);

printf("解码结果为:\n");

HuffmanDecode(ch, hufmTree, string);

printf("\n");

free(arr);

free(hufmTree);

return0;

}

运行结果如图:

三、程序实现过程当中遇到的问题总结

1)关于哈夫曼树,知道了叶子结点,如何不用静态数组存储整个哈夫曼树及构建过程中的生成树?

答:使用malloc函数开辟一段内存空间存结构体类型的树,若往树中添加新的结点挂在结构体指针上即可,这就要求定义的结构体里面包含结构体指针,这也是结构体指针的作用。也就是使用链表动态存储,每个结点都是一个包含结构体指针的结构体,生成过程中动态开辟,不管这棵树有多少个结点都可以存下。

2)

[cpp]view plaincopy

typedefstructstHuNode

{

intdata;// 权值

structstHuNode* lchild;//这两句等价于 struct stHuNode* lchild, *rchild;

structstHuNode* rchild;

}HUNODE;

3)scanf语句里不要有换行符?scanf函数的用法,scanf(" %d", &i);和scanf("%d ",&i);效果不同,差别在哪?

答:scanf函数的一般形式为:scanf(“格式控制字符串”, 地址表列);格式控制字符串中不能显示非格式字符串,也就是不能显示提示字符串和换行符。” %d” 和“%d”作用一样,%d前面的空格不起作用,”%d “空格加在%d后面赋值时要多输入一个值,实际赋值操作时多输入的数并没有被赋值只是缓存到了输入流stdin中,下次如果再有scanf和gets语句要求赋值时,要先用fflush(stdin);语句强制清除缓存再赋值,否则原先在stdin中的值就会被赋过去导致错误。

4)灵感:怎样解码?根据输入的01串解出相应的权值?

答:No!如果两个不同的字符对应的权值相同呢?如何区分?起初想到如果在建立哈夫曼树的过程中可以记录下相应的下标就不会导致相同权值无法区别的问题,但在具体如何实现上,刚开始想输出每一次建树的small1和small2,但发现这样很不清晰,用户要想确定每个字符的下标得按照建树过程走一遍才行,那要程序何用,而且给用户造成了很大的麻烦,不可取。后来想到,可以在结点的结构体中添加id信息,这样即使权值相同的结点也可以区分开来,这里的id可以是下标,因为用户输入权值的顺序一定则下标唯一。如果解码解出来的是权值标号的话就没有异议了,可是下标又不是很直观清晰,不如直接输出相应的字符好,又想到两个解决办法:a)将结点id信息直接定义成字符,只不过在建树的过程中要将字符和权值都加入结点中;b)id仍然是下标,在用户输入权值对应的字符时,用字符数组存储并和id对应起来,这样解码得到id之后,可以输出对应的字符,实现起来相对比较简单。

5)疑问:如果根据用户输入的字符串进行编码解码得到了新的字符串,旧字符串和新字符串有没有直接看出来的规律,就是说人眼观察和推导能否得到相应的规律,或者说有没有可能直接能得出规律不用经过编码解码。留为疑问!

答:后经测试发现不行。

6)gets()函数的用法,如何获取一个字符串,赋值时跳过gets()函数的执行,貌似gets()没起作用的问题。

答:当使用gets()函数之前有过数据输入,并且,操作者输入了回车确认,这个回车符没有被清理,被保存在输入缓存中时,gets()会读到这个字符,结束读字符操作。因此,从用户表面上看,gets()没有起作用,跳过了。

解决办法:

方法一、在gets()前加fflush(stdin); //强行清除缓存中的数据(windows下可行)

方法二、根据程序代码,确定前面是否有输入语句,如果有,则增加一个getchar()命令,然后再调用 gets()命令。

方法三、检查输入结果,如果得到的字符串是空串,则继续读入,如:

[cpp]view plaincopy

charstr[100]={0};

do{

gets(str);

}while( !str[0] );

7)初始化数组语句:memset(str,0, sizeof(str)); 理解一下!

答:函数解释:将 str 中前 n 个字节用 ch 替换并返回 str。memset(str, 0, sizeof(str));意思是将数组str的长度(字节数,不是元素个数)置零。

memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。

8)关于strlen()函数

答:strlen函数的用法,包含头文件string.h。且是对字符数组中的字符串求长度!

其原型为:  unsigned int strlen (char *s);

【参数说明】s为指定的字符串。

strlen()用来计算指定的字符串s 的长度,不包括结束字符"\0"。

【返回值】返回字符串s 的字符数。

注意:strlen() 函数计算的是字符串的实际长度,遇到第一个'\0'结束。如果你只定义没有给它赋初值,这个结果是不定的,它会从首地址一直找下去,直到遇到'\0'停止。而sizeof返回的是变量声明后所占的内存数,不是实际长度,此外sizeof不是函数,仅仅是一个操作符,strlen()是函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容