哈夫曼编码问题

本文深入探讨哈夫曼编码原理,展示如何根据字符出现频率构建哈夫曼树,并生成变长编码,实现数据无损压缩。以英文字符为例,说明高频率字符使用短编码,低频率字符使用长编码,从而有效降低平均编码长度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

哈夫曼编码

1、介绍
      在计算机数据处理中,哈夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符号出现几率的方法得到的,出现几率高的字母使用较短的编码,反之出现几率低的则使用较长的编码,这便使编码之后的字符串的平均长度、期望值降低,从而达到无损压缩数据的目的。
      例如,在英文中,e的出现几率最高,而z的出现概率则最低。当利用哈夫曼编码对一篇英文进行压缩时,e极有可能用一个比特来表示,而z则可能花去25个比特(不是26)。用普通的表示方法时,每个英文字母均占用一个字节,即8个比特。二者相比,e使用了一般编码的1/8的长度,z则使用了3倍多。倘若我们能实现对于英文中各个字母出现概率的较准确的估算,就可以大幅度提高无损压缩的比例。
      哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树。所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明哈夫曼树的WPL是最小的。
      本文求解的是,字母表{A, C, G, T},它们在一个长串中出现的频率分别是0.31, 0.2, 0.09, 0.4。试给出四个字母的Huffman编码。

2、代码

#include <stdio.h>
#include <malloc.h>

#define N 4 //数组长度

char code[N];    //存储哈夫曼编码

typedef struct Node {    //哈夫曼树结构体
    int data;    //权值
    char c;    //字符
    struct Node *lchild;    //左子树
    struct Node *rchild;    //右子树
} TreeNode;

typedef struct {    //存储输入要生成哈夫曼编码的数组
    int data;    //权值
    char c;    //字符
} Weight;

Weight Num[] = {    //要编码的字符,权值放大100倍
        {31, 'A'},    //0.31
        {20, 'C'},    //0.2
        {9,  'G'},    //0.09
        {40, 'T'},    //0.4
};

//查找权值小的下标
void FindLetter(int *pInt, int *pInt1, TreeNode **pNode, int l) {
    int i, data, mark, mark1;
    mark = *pInt;
    mark1 = 0;
    data = pNode[*pInt]->data;
    for (i = 0; i < l; ++i) {
        if (pNode[i] == NULL) {
            continue;
        }
        if (data > pNode[i]->data) {
            data=pNode[i]->data;
            mark = i;    //最小权值下标
        }
    }
    data = pNode[*pInt]->data;    //从数组第一个开始比较
    for (i = 0; i < l; ++i) {
        if (pNode[i] == NULL || i == mark) {
            continue;
        }
        if (data >= pNode[i]->data) {
            data=pNode[i]->data;
            mark1 = i;    //次小权值下标
        }
    }
    *pInt = mark;
    *pInt1 = mark1;
}

//构建哈夫曼树
TreeNode *CreateHuffman(int l) {
    int i, k1, k2;
    k1 = k2 = 0;
    TreeNode *p = NULL;    //最终存储哈夫曼树的数组
    TreeNode **mid = (TreeNode **) malloc(sizeof(TreeNode *) * l);    //构造哈夫曼树的一个中间过渡动态数组
    for (i = 0; i < l; ++i) { //初始化中间状态数组
        mid[i] = (TreeNode *) malloc(sizeof(TreeNode));
        mid[i]->c = Num[i].c;    //字符存入动态数组
        mid[i]->data = Num[i].data;    //权值存入动态数组
        mid[i]->lchild = NULL;
        mid[i]->rchild = NULL;
    }
    for (i = 0; i < l - 1; ++i) {   //循环合并构造树,循环字符串总数l-1次
        FindLetter(&k1, &k2, mid, l);    //查找最小权值的数组下标k1,以及次小的下标k2.
        p = (TreeNode *) malloc(sizeof(TreeNode));
        p->data = mid[k1]->data + mid[k2]->data;    //合并节点
        p->lchild = mid[k1];
        p->rchild = mid[k2];
        mid[k1] = p;
        mid[k2] = NULL;
    }
    free(mid);
    return p;
}

//构造哈夫曼编码
void PreHuffOrder(TreeNode *p, int i, char c) {
    if (p != NULL) {
        if (i > 0) {
            code[i - 1] = c;                   //在父节点的基础上添加新的编码(根节点不添加)
        }
        PreHuffOrder(p->lchild, i + 1, '0');    //左子树编码0
        PreHuffOrder(p->rchild, i + 1, '1');    //右子树编码1
        if (p->lchild==NULL&&p->rchild==NULL) {    //判断叶子节点
            printf("%c: ", p->c);    //输出字符
            printf("%s\n", code);   //输出当前字符编码
            code[i-1]=0;    //完成操作后将刚填入的编码置0
        }
    }
}

//主函数
int main() {
    TreeNode *p;
    p = CreateHuffman(N);    //构造哈夫曼树
    PreHuffOrder(p, 0, '0');    //输出哈夫曼编码
    return 0;
}

3、相关文件:Github

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值