数据结构哈夫曼编码算法c语言,算法与数据结构(c语言)——赫夫曼树&赫夫曼编码...

本文介绍了赫夫曼树的基本概念,包括带权路径长度、构造过程以及其在数据压缩中的应用。通过C语言实现,展示了如何利用赫夫曼编码对字符进行高效编码,以及编码和解码的过程。重点在于理解如何从n个权值生成最优的二叉树结构并生成相应的编码规则。
摘要由CSDN通过智能技术生成

赫夫曼树:最优二叉树,带权路径长度最短的树,也称为赫夫曼树。

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为赫夫曼树(Huffman Tree)。赫夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

先说几个基本的名词:

路径:从树的一个结点到另一个结点之间的分支构成两个结点之间的路径

路径长度:路径上的分支数目

树的路径长度:从树根到每个结点的路径长度之和

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

赫夫曼树:带权路径长度WPL(Weighted Path Length)最小的二叉树。

构造一颗赫夫曼树

假设有n个权值,则构造出的赫夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则赫夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的赫夫曼树

赫夫曼编码

翠花说先上几个基本的名词:

定长编码:像是ASCII编码就是定长的,都是8位表示一个字符。

变长编码:单个字符的编码的长度不一致,可以根据整体出现的频率来进行调节。

前缀码:给定一个序列的集合,若不存在一个序列是另一个序列的前缀,则该序列集合称为前缀码(就是没有任何码字是其他码字的前缀)。

C代码实现

定义的头文件:huffman.h

#include #include #include //错误

#defineERROR0

//成功

#define SUCCESS1

/* 状态码识别类型 */

typedef int Status;

#define MAXSIZE 1024

typedef char Element;

typedef struct {

// 节点值

Element data;

// 权重

unsigned int weight;

// 父结点,左孩子,右孩子

unsigned int parent,lchild,rchild;

}HTNode;

typedef HTNode* HuffmanTree;

typedef Element* HCNode;

/*

用以存储根据huffman树来生成每个字符的huffman编码值

例如:

data: , code: 10

data:a, code: 111

data:h, code: 110

data:i, code: 001

data:o, code: 0101

data:s, code: 0100

data:x, code: 0111

data:y, code: 0110

data:z, code: 000

*/

typedef HCNode* HuffmanCode;

/*

对于有n个叶子节点的赫夫曼树,共有2n-1个节点。

由二叉树性质:对于任何一个二叉树T,如果其终端节点数为n_0,度为2的节点数为n_2,则n_0 = n_2 +1。

赫夫曼树是个严格的二叉树,不存在度为1的节点。

so,赫夫曼树的节点 = 度为2的节点 + 叶子节点,即2n-1

*/

HuffmanTree buildTree(char *str);

void Create_Huffmantree(int n, int m, HuffmanTree HT);

Status HuffmanCoding(HuffmanTree HT, HuffmanCode *HC);

Status EnCoding(HuffmanCode HC, char *str, char **code);

Status DeCoding(HuffmanTree HT, char *code, char **translation);

huffman.c

#include "huffman.h"

// 记录有效的字符个数

int valid_char_num = 0;

// 记录有效字符的指针

char *valid_char_pointer;

HuffmanTree buildTree(char *str) {

// ASCII编码就只能表示256个字符

valid_char_pointer = (char *)malloc(256 * sizeof(char));

// 用来记录每个字符出现的次数

int char_count_arr[256] = {0};

//并计算每个符号出现的次数

for(int i=0; str[i]!='\0'; i++) {

// 以字符的ascill码作为索引,在数组中的对应位置记录次数。

char_count_arr[(unsigned char) str[i]]++;

}

// 循环存放有效字符

for(int i = 0; i < 256; i++) {

if(char_count_arr[i] != 0) {

valid_char_pointer[valid_char_num++] = (char)i;

}

}

// 加上结束标志。

valid_char_pointer[valid_char_num] = '\0';

// 总共需要m个节点来存储整个树

int m = valid_char_num * 2 - 1;

// 申请一连串的存储空间

HTNode *ht_nodes = (HTNode *)calloc(m, sizeof(HTNode));

if(!ht_nodes) {

exit(1);

}

// 节点进行初始化

for(int i = 0; i < m; i++) {

if(i < valid_char_num) {

ht_nodes[i].data = valid_char_pointer[i];

ht_nodes[i].weight = char_count_arr[(unsigned char) valid_char_pointer[i]];

} else {

ht_nodes[i].data = '\0';

ht_nodes[i].weight = 0;

}

ht_nodes[i].lchild = ht_nodes[i].rchild = ht_nodes[i].parent = -1;

}

HuffmanTree ht = ht_nodes;

Create_Huffmantree(valid_char_num, m, ht);

return ht;

}

/*

寻找权值最小的两个数,然后合并放入n ~ m-1个结点之中。对叶子节点进行操作合并。

n是叶子结点的个数,m=2n-1,从下标0开始存储

*/

void Create_Huffmantree(int n, int m, HuffmanTree HT) {

for (int i = n; i < m; i++) {

// 32767是整型在16位计算机中的最大值,w1记录最小权值,w2存储第二小的权值

int w1 =32767, w2 = 32767;

// p1记录最小权值下标,p2存储第二小的权值下标

int p1 = -1, p2 = -1;

// 必须是小于i,若等于i,那么最小的权值就是当前i节点的权值:0。

// 同样不能是n,因为合并后的节点同样要参与比较。

for (int j = 0; j < i; j++) {

// 若为叶子节点

if (HT[j].parent == -1) {

if (HT[j].weight <= w1) {

w2 = w1;

w1 = HT[j].weight;

p2 = p1;

p1 = j;

} else if (HT[j].weight < w2) {

w2 = HT[j].weight;

p2 = j;

}

}

}

if (p1 > -1 && p2 > -1) {

// 将最小的两个节点权值相加赋给合并的节点

HT[i].weight = w1 + w2;

// 左孩子为最小的,右孩子为第二小的

HT[i].lchild = p1;

HT[i].rchild = p2;

HT[p1].parent = i;

HT[p2].parent = i;

}

printf("\n------huffman tree Construction process------%d-th time------\n",i - valid_char_num + 1);

for(int i = 0; i < valid_char_num * 2 - 1; i++) {

printf("data:%c, weight:%d, parrent:%d, lchild: %d, rchild:%d\n", HT[i].data, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);

}

printf("\n");

}

}

/* 从叶子节点开始逆向进行赫夫曼编码*/

/*HC用来储存huffman编码值*/

Status HuffmanCoding(HuffmanTree HT, HuffmanCode *HC) {

int n = valid_char_num;

if(!(*HC = (HuffmanCode)malloc(n * sizeof(HCNode)))) {

return ERROR;

}

/* 声明一个动态字符数组code,用来临时存储赫夫曼编码,

n个叶子节点的huffman tree,深度最多就为n(没验证过,只为理解),

那一个字符的编码长度最大也就n-1个。所以有n个存储空间就够了,最后一个存放\0。

*/

HCNode code;

if(!(code = (HCNode)malloc(n * sizeof(char)))) {

return ERROR;

}

/* 加上一个字符串结束符号*/

code[n - 1] = '\0';

for(int i = 0; i < n; i++) {

// 记录当前元素的位置

int current = i;

int father = HT[i].parent;

// 从后向前记录的索引位置

int end = n - 1;

// 这里的huffman tree根节点的parent域为-1

while(father != -1) {

// 若当前节点等于当前节点的父节点的左孩子,编码为0,右孩子编码为1

if(current == HT[father].lchild) {

code[--end] = '0';

} else {

code[--end] = '1';

}

// 父节点为当前元素节点。

current = father;

// 父节点指向父节点的父节点

father = HT[father].parent;

}

/* HC[i]用于最终存储huffman码,是char类型的数组,有n个char类型的数据*/

if(!(*(*HC + i) = (HCNode)malloc((n - 1 - end) * sizeof(char)))) {

return ERROR;

}

/* 从临时空间中复制到HC[i]中*/

strcpy(*(*HC + i), code + end);

}

/*释放临时存储空间*/

free(code);

code = NULL;

printf("\n===============huffman code==================\n");

for(int i = 0; i < n; i++) {

printf("data:%c, code: %s\n", HT[i].data, *(*HC + i));

}

return SUCCESS;

}

Status EnCoding(HuffmanCode HC, char *str, char **code) {

printf("\n===============Encode==================\n");

if(!(*code = (char *)malloc(MAXSIZE * sizeof(char)))) {

return ERROR;

}

**code = '\0';

int len = strlen(str);

for(int i = 0; i < len; i++){

for(int j = 0; j < valid_char_num; j++) {

if(*(str + i) == valid_char_pointer[j]) {

//printf("%s", *(HC + i));

strcpy(*code + strlen(*code), *(HC + j));

break;

}

}

}

return SUCCESS;

}

Status DeCoding(HuffmanTree HT, char *code, char **translation) {

printf("\n===============Decode==================\n");

if(!(*translation = (char *)malloc(sizeof(char)))) {

return ERROR;

}

// 编码的长度

int n = strlen(code);

int j = 0;

for(int i = 0; i < n; ) {

// 每次都从根节点开始

HTNode t = HT[valid_char_num * 2 - 2];

// 只要存在孩子节点就判断第i个编码是0还是1

while((int)t.lchild > -1 || (int)t.rchild > -1) {

if(code[i++] == '0') {

// 编码为0将t的左孩子赋值给t

t = HT[t.lchild];

} else {

// 编码为1将t的右孩子赋值给t

t = HT[t.rchild];

}

}

*(*translation + j++) = t.data;

}

// 加上一个字符串结束标志

*(*translation + j) = '\0';

return SUCCESS;

}

main:

#include "huffman.h"

int main(int argc, char *argv[]) {

//char *str = "aabc ebac fv afvvvv";

char *str = "shi xiao zha zha ya";

HuffmanTree ht = buildTree(str);

HuffmanCode hc;

HuffmanCoding(ht, &hc);

char *code = NULL;

EnCoding(hc, str, &code);

printf("%s 得到的huffman编码为:%s\n", str,code);

char *translation;

char *cd = "010011000110011100111101011000011011110000110111100110111";

DeCoding(ht, cd, &translation);

printf("huffman编码 %s \n译文:%s\n", cd, translation);

return 0;

}

运行结果:

d02d616ffa244ef52f4c00dd438e94d7.png

7b480533a2f67f0afdf74ba9b8db9d7a.png

代码有些是东拼西凑从别处摘的,参考的多篇博客,某些出处都忘了,所有就都不贴了,抱歉。

错误不足之处还请指正。3Q!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值