22、数据压缩-霍夫曼编码

本文介绍霍夫曼编码的原理及其实现步骤,包括最小冗余编码的概念、霍夫曼树的创建、编码表的构建、数据的压缩与解压等关键环节。

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

1、最小冗余编码:给出现的符号编码,使用较少的位对应出现频率高的符号编码,较多的位给出现频率较低的符号编码。霍夫曼编码基于此。
这里写图片描述
2、数据结构

//霍夫曼树的节点,即data指向的内容,包含符号标记(就是数组下标)和频率
typedef struct Huffnode_
{
    unsigned char symbol;
    int freq;
}HuffNode;


//每个符号对应的编码表,used:符号有没有被使用;code:对应编码;szie:编码的位数
typedef struct HuffCode_{
    unsigned char used;
    unsigned short code;
    unsigned char size;
}HuffCode;

3、创建霍夫曼树,最终生成的霍夫曼树最底层为所有符号节点
(1)先为每个符号建一棵树,这个符号作为根节点插入,并把这些单个符号入队列
(2)出队列2个最小的点 ,把他们合成一个树,把这个合成的树再加入队列,循环直到合成最后一颗树:霍夫曼树。
(3)最后把生成的霍夫曼树出口给tree指针

static int build_tree(int *freqs, BIT_TREE **tree)
{
    BIT_TREE *init, *merge, *left, *right;
    PQueue pqueue;
    HuffNode *data;
    int size, c;

    *tree = NULL;
    pqueue_init(&pqueue, compare_freq, destroy_tree);

    for(c = 0; c <= UCHAR_MAX; c++)
    {
        if(freqs[c] != 0)
        {
            //先为每个符号建一棵树,这个符号作为根节点插入 
            init = (BIT_TREE *)malloc(sizeof(BIT_TREE));
            bittree_init(init, free);

            data = (HuffNode *)malloc(sizeof(HuffNode));
            data->symbol = c;
            data->freq = freqs[c];
            bittree_ins_left(init, NULL, data); 

            //把这些单个符号入队列
            //优先队列,出队列是头部最大或最小,入队列从后面入 
            pqueue_insert(&pqueue, init);
        }
    }

    size = pqueue_size(&pqueue);
    for(c = i;c <= size - 1; c++)
    {
        merge = (BIT_TREE *)malloc(sizeof(BIT_TREE));

        //出队列连个最小的点 ,把他们合成一个树
        //把这个合成的树再加入队列,循环直到合成最后一颗树:霍夫曼树
        //最终生成的霍夫曼树最底层为所有符号节点 
        pqueue_extract(&pqueue, (void **)&left);
        pqueue_extract(&pqueue, (void **)&right);

        data = (HuffNode *)malloc(sizeof(HuffNode));
        memset(data, 0, sizeof(NuffNode)); 
        data->freq = ( (HuffNode *)bittree_data(bittree_root(left)) )->freq + ( (HuffNode *)bittree_data(bittree_root(right)) )->freq;
        bittree_merge(merge, left, right, data);
        pqueue_insert(&pqueue, merge);

        //需要手动释放,不然每次循环都在申请空间 
        free(left);
        free(right); 
    } 

    //最后把生成的霍夫曼树出口给tree指针 
    pqueue_extract(&pqueue, (void **)tree);

    return 0; 
}

4、建表

//table为最终生成的表,每个元素是每组符号和其对应的编码 
static void build_table(BIT_TREE_NODE *node, unsigned short code, unsigned char size, HuffCode *table) 
{
    if(node->left != NULL)
        //向下遍历,左边编码补0 
        build_table(node->left, code << 1, size + 1, table);
    if(node->right != NULL)
        //向下遍历,左边编码补0 
        build_table(node->right, (code << 1) | 0x0001, size + 1, table);

    //符号节点都在最底层,即无左右子叶 
    if((node->left == NULL) && (node->right == NULL))
    {
        //保证编码为大端模式 
        code = htons(code); 

        //符号就是下标,8位数据以自身对应的大小为符号。没出现的值为0,used = 0 
        table[ ( (HuffNode *)(node->data) )->symbol ].used = 1;
        table[ ( (HuffNode *)(node->data) )->symbol ].code = code;
        table[ ( (HuffNode *)(node->data) )->symbol ].size = size;

    }
}

5、数据压缩
压缩过程建的数和表只是过程量,不占空间。最终的压缩数据只多了头信息(原数据长度 + 每个符号出现的次数)

//original:原始数据;compressed:压缩后数据;size:原始数据的字节数 
int huffman_compress(const unsigned char *original, unsigned char **compressed, int size)
{
    BIT_TREE *tree;
    HuffCode table[UCHAR_MAX + 1];
    int freqs[UCHAR_MAX + 1], max, scale, hsize, ipos, opos, cpos, c, i;
    unsigned char *comp, *temp;

    //初始化操作 
    *compressed = NULL;
    for(c = 0; c <= UCHAR_MAX; c++)
        freqs[c] = 0;

    //t统计符号出现的频率,没出现的保持为0           
    ipos = 0;   
    if(size > 0)
    {
        while(ipos < size)
        {
            freqs[original[ipos]]++;
            ipos++;
        }
    } 

    //为压缩频率做准备,如果有符号出现次数大于符号个数UCHAR_MAX,
    //则max为最多的那个元素,否则max = UCHAR_MAX(不压缩);
    //压缩的目的是使每个频率数值都能用叫少的位表示,
    //数据种类最多为UCHAR_MAX,那么频率的变化也不会超过UCHAR_MAX种; 
    max = UCHAR_MAX;
    for(c = 0; c <= UCHAR_MAX; c++)
    {
        if(freqs[c] > max)
            max = freqs[c];
    } 

    for(c = 0; c <= UCHAR_MAX; c++)
    {
        scale =(int)(freqs[c] / ( (double)max / (double)UCHAR_MAX ));

        //压缩后的数据为零点几,存为1 
        if(scale == 0 && freqs[c] != 0)
            freqs[c] = 1;
        else
            freqs[c] = scale;
    } 

    //建树 
    build_tree(freqs, &tree); 

    for(c = 0; c <=UCHAR_MAX; c++)
        memset(&table[c], 0, sizeof(HuffCode));

    //建表 
    build_table(tree->root, 0x0000, 0, table); 

    bittree_destroy(tree);
    free(tree);

    //建立头信息,大小为 4 + UCHAR_MAX + 1  个字节 
    hsize = sizeof(int) + (UCHAR_MAX + 1);
    comp = (unsigned char *)malloc(hsize);

    //前4个字节存带压缩数据的总字节数 
    memcpy(comp, &size, sizeof(int)); 

    //后面每个字节存放每个符号出现的频率 
    for(c = 0; c <= UCHAR_MAX; c++)
        comp[sizeof(int) + c] = (unsigned char)freqs[c]; 

    ipos = 0;
    opos = hsize * 8;

    //压缩后的数据前面放头信息,之后放每个数据对应的编码 
    while(ipos < size)
    {
        c = original[ipos];

        //table[c].size为编码的位数, 不能对这些为直接赋值,只能一位一位操作 
        for(i = 0; i < table[c].size; i++)
        {
            //opos每次 + 1bit,加到一次8,即多一字节循环一次 
            if(opos % 8 == 0)
            {
                temp = (unsigned char *)realloc(comp, (opos / 8) + 1);
                comp =temp;
            }

            //从当前符号的编码中取出一位给cpos,从高位先取,如5位编码code=0b10101=0b000_10101 
            cpos = (sizeof(short) * 8) - table[c].size + i; 
            bit_set(comp, opos, bit_get((unsigned char *)&table[c].code, cpos));
            opos++;
        }
        ipos++; 
    }

    //返回压缩后的数据字节数 
    *compress = comp;
    return ((opos - 1) / 8) + 1;
}

6、数据解压,并不需要建表,根据编码的0、1左右向下找到最底层树节点即找到该符号。找到的数据为字节数据可以直接赋值。而压缩时必须建表,因为霍夫曼树不能向上遍历,所以不能根据符号从树中直接得到编码。

int huffman_uncompress(const unsigned char *compressed, unsigned char **original)
{
    BIT_TREE *tree;
    BIT_TREE_NODE *node;
    int freqs[UCHAR_MAX + 1];//0到UCHAR_MAX是UCHAR_MAX+1个数
    int hsize, size, ipos, opos, state, c;
    unsigned char *orig, *temp;

    *original = orig = NULL;

    //从压缩数据的头信息中读出数据长度和符号频率数组 
    hsize = sizeof(int) + (UCHAR_MAX + 1);
    memcpy(&size, compressed, sizeof(int));

    for(c = 0; c <= UCHAR_MAX; c++)
        freqs[c] = compressed[sizeof(int) + c];

    build_tree(freqs, &tree);
    ipos = hsize * 8;//ipos是压缩数据中的第几bit位 
    opos = 0;//解压后数据的第几个字节 
    node = tree->root;

    while(opos < size)
    {
        state = bit_get(compressed, ipos);
        ipos++;
        if(state == 0)
            node = node->left;
        else
            node = node->right;

        if((node->left == NULL) && (node->right == NULL))
        {
            if(opos == 0)
                orig = (unsigned char *)malloc(1);
            else
            {
                temp = (unsigned char *)realloc(orig, opos + 1);
                orig = temp;
            }

        //此时找到的数据为字节数据可以直接赋值 
            orig[opos] = ((HuffNode *)bittree_data(node))->symbol;
            opos++;

            //回到树顶点为下次遍历准备 
            node = tree->root;
        } 
    }
    bittree_destroy(tree);
    free(tree);

    *original = orig;
    return opos; 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值