哈夫曼树以及哈夫曼编码在文件压缩上的应用

哈(赫)夫曼树和哈(赫)夫曼编码
  • 路径 一个结点到另外一个结点的通路,称为路径 (祖先结点到子孙结点)
  • 路径长度: 每经过一个结点,路径长度就增加1,不包括起始结点的
  • 结点权值:对于结点赋予一个数值,表示结点的权值 比较:结点元素出现的次数
  • 带权路径长度: 从根点出发到该结点的路径长度 乘以 该结点的权值
  • 树的带权路径长度 (WPL): 树中所有叶子结点的带权路径长度之和
  • 哈夫曼树:最优二叉树,由n个叶结点组成的二叉树的带权路径长度最短
    • 结点相同,构成的哈夫曼树可能不唯一,但树的带权路径长度相等
    • 把n个结点构成哈夫曼树,这n个结点必然作为叶子结点,需要添加n-1个分支结点
构建哈夫曼树
  • 遵循的原则:权重越大离根结点越近
  • 算法描述过程
      1. 把n个叶结点看作n棵独立的树,构成森林F
      1. 创建一个新的结点,然后从森林F中选取两棵根结点权值最小的树作为新结点的左右子树,并且把新的结点的权值设置为这两棵树根结点权值之和
      1. 从森林F中把刚才选取两棵树删除,并且把新的结点作为树的根结点加入到森林中
      1. 重复2和3的步骤,直到森林中只剩下一棵树为止
A:3   B:4     C:1    D:5    E:10     F:6    G:2

E:10   F:6    D:5    B:4   A:3    G:2   C:1

   3
 /   \
G     C
E:10   F:6    D:5    B:4  A:3    GC:3
   6
 /  \ 
A    3
    / \
   G   C
E:10   F:6   A GC:6  D:5    B:4
  9
 / \
D   B

Fx:12  E:10  DB:9  
   12
   / \
  F   6
     /  \ 
    A    3
        / \
       G   C
哈夫曼树:
       31
      /  \
   19      12
  /  \    /  \
 E    9  F    6
     / \     / \
    D   B   A   3
               /  \
              G    C
A:3   B:4     C:1    D:5    E:10     F:6    G:2
E:00   D:010  B:011  F:10   A:110  G:1110  C:1111

3x3 + 4x3 + 1x4 + 5x3 + 10x2 + 6x2 + 2x4  
= 9 + 12 + 4 + 15 + 20 + 12 + 8
哈夫曼树特性
  1. 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
  2. 哈夫曼树的结点总数为2n − 1
  3. 哈夫曼树中不存在度为1的结点
  4. 哈夫曼树并不唯一,但WPL必然相同且为最优
哈夫曼编码
  • 编码
    • 定义编码
    • ASCII 定长编码 每个字符都是8个二进制位
      • A 65 0100 0001
      • B 66 0100 0010
      • C 67 0100 0011
      • D 68 0100 0100
    • unicode 万国码 4byte
    • 变长编码 utt-8 中文汉字2-4byte gbk 2byte
    • 哈夫曼编码就是变长编码
    • 哈夫曼编码是前缀编码 任意一个字符的编码都不会是另外一个字符编码的前缀
    • 前缀编码不会有歧义 非前缀编码会产生歧义

100道选择题: 按照ascii发送: 传输100byte

A: 00 B: 01 C: 10 D: 11 100*2bit/8 byte = 25byte

A:50 B:20 C:15 D:15

A:0 B:10 C:110 D:111 50x1+20x2+30x3bit/8 = 180/8 = 22.5byte

A:0 B:00 C:010 D:011 非前缀编码

  • 由哈夫曼树获取哈夫曼编码

    • 从根结点出发,左子树为0,右子树为1,到所有叶子结点所经过的路径就构成了哈夫曼编码
文件的压缩和解压
  • 利用哈夫曼编码对文件进行压缩和解压
  • 算法过程
    • 读取文件中的内容,统计每一个字符出现的次数
    • 根据得到的字符-对应的次数构建哈夫曼树,得到哈夫曼编码
    • 对文件中的字符用哈夫曼编码进行压缩
    • 为了能够解压,需要把哈夫曼树存储的到文件中

文件的压缩和解压

利用哈夫曼树对文件进行压缩:

  • 压缩原因:在ACSLL 的编码下,存储一个字符需要使用8位二进制, 而随着计算机的普及,各个国家的文字编码各不相同,随之万国码(Unicode)应运而生,为各个国家的语言设定了二进制编码,以满足跨语言的文本处理要求,但是通用性的增强也带来了空间使用上的麻烦,万国码存储一个字符所需的容量为四个字节,远大于使用ascll码时所需的空间,那么这时候如果有针对文件中的字符进行统一规则的重新编码,将大大节省空间,而哈夫曼编码正好可以实现这一目的。

  • 压缩原理:

    ​ 由于哈夫曼编码有权重属性,我们可以先遍历整个文件,将所有字符记录并且根据出现的次数对其进行赋权重。这样之后根据权重对所得的字符进行哈夫曼编码,由此就可以得到各个字符独特的哈夫曼编码,再重新将编码写入文件中,即可实现文件的压缩。

    huffman.c

#include "Huffman.h"

int compareWeight(const void *v1, const void *v2)
{
    HUFF *h1 = (HUFF*) v1;
    HUFF *h2 = (HUFF*) v2;
    //return ((*h2)->weight - (*h1)->weight);
    if((*h1)->weight < (*h2)->weight){
        return 1;
    }
    else if((*h2)->weight < (*h1)->weight){
        return -1;
    }
    else{
        return 0;
    }
}

HUFF create_HuffmanTree_by_file(HUFF tree[], size_t n)
{
    qsort(tree, n, sizeof(HUFF), compareWeight);
    HUFF root = NULL;
    for(int i = n-1; i > 0; i--){
        root = (HUFF)malloc(sizeof(struct HuffTree));
        root->lchild = tree[i-1];
        root->rchild = tree[i];
        root->weight = tree[i-1]->weight + tree[i]->weight;
        memset(root->code, 0, CODE_LEN);
        int j;
        for(j = i - 2; j >= 0 && tree[j]->weight < root->weight; --j){
            tree[j+1] = tree[j];
        }
        tree[j + 1] = root;
    }
    root = tree[0];
    return root;
}
//elems为元素, weight为权重
HUFF create_HuffmanTree(ElemType elems[], size_t weight[], size_t n)
{
    //HUFF *tree = (HUFF*)malloc(sizeof(HUFF*) * n);
    HUFF tree[n];
    if(NULL == tree){
        return NULL;
    }
    for(int i = 0; i < n; ++i){
        tree[i] = (HUFF)malloc(sizeof(struct HuffTree));
        tree[i]->data = elems[i];
        tree[i]->weight = weight[i];
        tree[i]->lchild = tree[i]->rchild = NULL;
        memset(tree[i]->code, 0, CODE_LEN);
    }

    return create_HuffmanTree_by_file(tree, n);

}
//获得哈夫曼编码
void get_HuffCode(HUFF tree)
{
    if(NULL != tree){
        if(tree->lchild != NULL){
            strcpy(tree->lchild->code,tree->code);
            strcat(tree->lchild->code, "0");
            get_HuffCode(tree->lchild);
        }
        if(tree->rchild != NULL){
            strcpy(tree->rchild->code,tree->code);
            strcat(tree->rchild->code, "1");
            get_HuffCode(tree->rchild);
        }
    }
}

//输出哈夫曼编码
void foreach_HuffmanTree(HUFF tree, void (*visit)(ElemType, size_t, char*))
{
    if(tree != NULL){
        if(tree->lchild == NULL && NULL == tree->rchild){
            visit(tree->data, tree->weight, tree->code);
        }
        else{
            foreach_HuffmanTree(tree->lchild, visit);
            foreach_HuffmanTree(tree->rchild, visit);
        }
    }
}
//
void destroy_HuffmanTree(HUFF tree)
{
    if(tree != NULL){
        destroy_HuffmanTree(tree->lchild);
        destroy_HuffmanTree(tree->rchild);
        free(tree);
    }
}

#define BUFF_LEN 1024

HUFF bulid_huffmantree_by_file(const char *file,const char *dfile,HC *phfm)
{
    FILE *fp = fopen(file, "rb");//binary二进制模式, text文本模式,二者在Linux里没有任何区别,但在Windows下,text模式中会将'\n'转化为"\r\n"存储,取出时同样转化
    if(NULL == fp){
        return NULL;
    }

    unsigned char buf[BUFF_LEN] ={};//使用无符号字符编码防止出现其他类型字符超出范围的情况
    size_t cnt = 0;
    int i;
    while((cnt = fread(buf, 1, BUFF_LEN, fp)) > 0){
        //从文件中读到cnt个字符
        for(i = 0; i < cnt; i++){
            unsigned char b = buf[i];
            if(phfm->index[b] == NULL){//新字符,添加
                HUFF node = (HUFF)malloc(sizeof(struct HuffTree));
                node->lchild = node->rchild = NULL;
                node->data = b;
                node->weight = 1;
                phfm->index[b] = node;
                phfm->nodes[phfm->len] = node;
                ++phfm->len;
            }
            else{
                ++phfm->index[b]->weight;//已有字符增加权重
            }
        }
    }
    size_t fs = ftell(fp);//获取文件读写位置    距离起始偏移的字节数 
    fclose(fp);
    fp = fopen(dfile,"wb");
    fwrite(&fs, sizeof(fs), 1, fp);
    fwrite(&phfm->len, sizeof(phfm->len), 1, fp);
    for(i = 0; i < phfm->len; ++i){
        fwrite(&phfm->nodes[i]->data, 1, 1, fp);
        fwrite(&phfm->nodes[i]->weight, sizeof(size_t), 1, fp);
    }
    fclose(fp);
    HUFF tree = create_HuffmanTree_by_file(phfm->nodes, phfm->len);//生成哈夫曼树
    if(tree == NULL){
        return NULL;

    }
    get_HuffCode(tree);//生成哈夫曼编码
    return tree;
}

unsigned char code_to_byte(const char *codes, int len)
{
    unsigned char byte = 0;
    int i;
    for(i = 0; i < len && i < 8; i++){
        byte = (byte << 1) | (codes[i] - '0');
    }
    for(; i < 8; ++i){
        byte = (byte << 1);
    }
    return byte;
}

static int compress_huffcode(const char *srcFile, const char *destFile, HUFF tree, struct HuffTree* index[])
{   
    FILE *fr = fopen(srcFile, "rb");
    if(NULL == fr){
        return FAILURE;
    }
    FILE *fw = fopen(destFile, "ab");
    if(NULL == fw){
        fclose(fr);
        return FAILURE;
    }
    unsigned char buf[BUFF_LEN] = {};
    size_t cnt = 0;
    int codelen = 0;
    unsigned char codes[BUFF_LEN * CODE_LEN] = {};
    while ((cnt = fread(buf, 1, BUFF_LEN, fr)) > 0){
        for(int i = 0; i < cnt; ++i){
            unsigned char b = buf[i];
            strcpy(codes + codelen, index[b]->code);
            codelen += strlen(index[b]->code);
        }
        unsigned char res[BUFF_LEN] = {};
        size_t n = 0;
        while(codelen >= 8){
            res[n] = code_to_byte(codes + n*8, codelen);
            codelen -= 8;
            ++n;
        }
        fwrite(res, 1, n, fw);
        if(codelen > 0){
            strncpy(codes, codes + 8*n, codelen);
            codes[codelen] = '\0';
        }
    }
    if(codelen > 0){
        unsigned char byte = code_to_byte(codes, codelen);
        fwrite(&byte, 1, 1, fw);
    }
    fclose(fr);
    fclose(fw);
    return SUCCESS;
}
//压缩入口
int compress(const char *srcFile, const char *destFile)
{
    assert(NULL != srcFile && NULL != destFile);
    if(strcmp(srcFile, destFile) == 0){
        return FAILURE;
    }
    HC huff = {};
    HUFF tree = bulid_huffmantree_by_file(srcFile, destFile, &huff);//从文件构建哈夫曼树
    if(NULL == tree){
        return FAILURE;
    }
    int ret = compress_huffcode(srcFile, destFile, tree, huff.index);
    destroy_HuffmanTree(tree);
    return ret;
}

HUFF get_huffman_by_file(FILE *fp, size_t *fs)
{
    fread(fs, sizeof(*fs), 1, fp);//读取原始文件大小
    size_t n = 0;
    fread(&n, sizeof(n), 1, fp);
    size_t weights[n];
    unsigned char datas[n];
    for(size_t i = 0; i < n; i++){
        fread(&datas[i],sizeof(datas[i]), 1, fp);
        fread(&weights[i],sizeof(weights[i]), 1, fp);
    }
    return create_HuffmanTree(datas, weights, n);
}

int code_to_char(const char *codes, unsigned char *byte, HUFF tree)
{
    HUFF node = tree;
    int i;

    for(i = 0; codes[i] != '\0'; ++i){
        if(codes[i] == '0'){
            node = node->lchild;
        }
        else{
            node = node->rchild;
        }
        if(node->lchild == NULL && node->rchild == NULL){
            *byte = node->data;
            return i+1;
        }
    }
    return 0;
}

int dencompression_file(FILE *fr,FILE *fw,HUFF tree,size_t fs)
{
    unsigned char buf[BUFF_LEN] = {};
    size_t cnt = 0;
    unsigned char codes[BUFF_LEN * sizeof(unsigned char)*8 + 1] = {};
    size_t codelen = 0;
    size_t sum = 0;
    while ((cnt = fread(buf, 1, BUFF_LEN, fr)) > 0){
        for(int i = 0; i < cnt; i++){
            for(int j = 7; j >= 0; --j){
                codes[codelen] = ((buf[i] >> j)&0x1) + '0';
                codelen++;
            }
        }
        codes[codelen] = '\0';
                unsigned char res[BUFF_LEN * sizeof(unsigned char) + 1] = {};
        size_t reslen = 0;
        size_t len = 0;
        size_t n = 0;
       
        while((len = code_to_char(codes + n, &res[reslen], tree)) > 0){
            n += len;
            reslen++;
        }
        if(sum + reslen > fs){
            reslen = fs - sum;
        }
        fwrite(res, 1, reslen, fw);
        sum += reslen;
        codelen -= n;
        if(codelen > 0){
            strncpy(codes, codes + n, codelen);
            codes[codelen] = '\0';

        }
    }   
    fclose(fr);
    fclose(fw);
}
//解压入口
int dencompression(const char *srcFile, const char *destFile)
{
    assert(NULL != srcFile && NULL != destFile);
    FILE *fr = fopen(srcFile, "rb");
    if(NULL == fr){
        return FAILURE;
    }
    FILE *fw = fopen(destFile, "wb");
    if(NULL == fw){
        fclose(fr);
        return FAILURE;
    }
    size_t fs = 0;
    HUFF tree = get_huffman_by_file(fr, &fs);
    
    get_HuffCode(tree);
    int ret = dencompression_file(fr, fw, tree, fs);
    destroy_HuffmanTree(tree);
    return ret;
}

Huffman.h

#ifndef _HUFFMAN_H_
#define _HUFFMAN_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>

#define SUCCESS 0
#define FAILURE -1
#define CODE_LEN 256
#define MAX_NODES 256
typedef unsigned char ElemType;

struct HuffTree{
    ElemType data;//
    size_t weight;//权重
    char code[CODE_LEN];//存储时转化为而精致
    struct HuffTree *lchild;
    struct HuffTree *rchild;
};

typedef struct HuffTree *HUFF;

struct HuffmaComprass{
    struct HuffTree *nodes[MAX_NODES];
    struct HuffTree *index[MAX_NODES];//存储不同字符
    size_t len;//记录nodes存储元素的个数

};

typedef struct HuffmaComprass HC;


HUFF create_HuffmanTree(ElemType elems[], size_t weight[], size_t n);//elems为元素, weight为权重
//获得哈夫曼编码
void get_HuffCode(HUFF tree);
//输出哈夫曼编码
void foreach_HuffmanTree(HUFF tree, void (*visit)(ElemType, size_t, char*));
//
void destroy_HuffmanTree(HUFF tree);

int compress(const char *srcFile, const char *destFile);

int dencompression(const char *srcFile, const char *destFile);

#endif// _HUFFMAN_H_

main.c(测试)

#include "Huffman.h"
#include <stdio.h>
#include <stdlib.h>

void printEWC(unsigned char e,size_t w,char *code){
	printf("%c    %2d    %s\n",e,w,code);
}


int main() {
	/*
	unsigned char elems[] = "ABCDEFGHIJKL";
	size_t weights[] = {5,2,3,9,7,8,6,1,12,10,15,4};
	HfmTree tree = create_hfmtree(elems,weights,12);
	hfmcode_hfmtree(tree);
	foreach_hfmtree(tree,printEWC);
	destroy_hfmtree(tree);
	*/
	compress("a.txt","a.mytar");
	dencompression("a.mytar","b.txt");
	return 0;
}

测试结果。
在这里插入图片描述

以上

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值