数据结构 哈夫曼树

哈夫曼树

定义

  • 路径 一个节点到另一个节点的通路,称为路径(祖先节点到子孙节点)
  • 路径长度:每经过一个节点,路径长度就增加1,不包括起始节点的
  • 节点权值:对于节点赋予一个数值,表示节点的权值 比较:节点元素出现的次数
  • 带权路径长度:从根节点出发到该节点的路径长度 乘以 该节点的权值
  • 树的带权路径长度(WPL):树中所有叶子节点的带权路径之和
  • 哈夫曼树:最优二叉树,由n个节点组成的二叉树的带权路径长度最短
    • 节点相同,哈夫曼树可能不唯一,但树的带权路径长度相等
    • 把n个节点构成哈夫曼树,这n个节点必然作为叶子节点,需要添加n-1个分支节点

构建哈夫曼树

  • 遵循的原则:权重越大离根节点越近
  • 算法描述过程
    1. 把n个叶子看作n棵独立的树,构成森林F
    2. 创建一个新的节点,然后从森林F中选取两棵节点权值最小的数作为新节点的左右子树,并且把的根节点设置为这两棵树根节点权值之和
    3. 从森林F中把刚才选取两棵树删除,并且把的节点作为树的根节点加入森林
    4. 重复2和3的步骤,直到森林中只剩下一棵树为止

哈夫曼树特性

  1. 每个初始结点最终都成为叶结点,且权值越小的结点到根结点的路径长度越大
  2. 哈夫曼树的结点总数为2n − 1
  3. 哈夫曼树中不存在度为1的结点
  4. 哈夫曼树并不唯一,但WPL必然相同且为最优

哈夫曼编码

  • 由哈夫曼树获取哈夫曼编码
    • 从根节点出发,左子树为0,右子树为1,到所有叶子节点所经过的路径就构成了哈夫曼编码

文件的压缩和解压

  • 利用哈夫曼树编码对文件进行压缩和解压
  • 算法过程
    • 读取文件中的内容,统计每一个字符出现的次数
    • 根据得到的字符-对应的次数构建哈夫曼树,得到哈夫曼编码
    • 对文件中的字符用哈夫曼编码进行压缩
    • 为了能够解压,需要把哈夫曼树存储到文件中

哈夫曼树实现(文件压缩和解压)

结构体定义

#define SUCCESS 0
#define FAILURE -1

typedef unsigned char ElemType;

#define CODE_LEN 256
#define MAX_NODES 256

// 哈夫曼树结点
struct HfmNode {
    ElemType data;     // 字符   节点的值     只有叶子节点才有值
    size_t weight;     // 权值  文件中data字符出现的次数
    char code[CODE_LEN];  // 哈夫曼编码  保存的是字符串   "010101010"  在存储时,需要转换为二进制
    struct HfmNode *lchild, *rchild;
};

// 实现压缩功能的结构体
struct HfmCompress {
    struct HfmNode *index[MAX_NODES];  // 用于记录    index[i]   表示i对应的哈夫曼节点
    struct HfmNode *nodes[MAX_NODES];  // 用于创建哈夫曼树   紧密存储
    size_t len;  // 记录nodes数组存储元素的个数
};

typedef struct HfmNode* HfmTree;

#define BUFF_LEN 1024

基本功能函数

// 创建哈夫曼树
HfmTree create_hfmtree(ElemType elems[], size_t weights[], size_t n);

// 用结点创建哈夫曼树
HfmTree create_hfmtree_by_hfmnodes(struct HfmNode *nodes[], size_t n);

// 通过哈夫曼树生成哈夫曼编码
void hfmcode_hfmtree(HfmTree tree);

基本功能实现

创建哈夫曼树 create_hfmtree
// 创建哈夫曼树
HfmTree create_hfmtree(ElemType elems[], size_t weights[], size_t n)
{
    // struct HfmNode **nodes = (struct HfmNode**)malloc(sizeof(struct HfmNode*) * n);
    struct HfmNode *nodes[n];
    if (nodes == NULL) {
        return NULL;
    }
    for (int i = 0; i < n; ++i) {
        nodes[i] = (struct HfmNode*)malloc(sizeof(struct HfmNode));
        nodes[i]->data = elems[i];
        nodes[i]->weight = weights[i];
        nodes[i]->lchild = nodes[i]->rchild = NULL;
        memset(nodes[i]->code, 0, CODE_LEN);  // 把nodes[i]->code数组全部清0
    }
    return create_hfmtree_by_hfmnodes(nodes, n);
}
用结点创建哈夫曼树 create_hfmtree_by_hfmnodes
int compareWeight(const void *v1, const void *v2)  // 比较函数
{
    struct HfmNode **h1 = (struct HfmNode**)v1;
    struct HfmNode **h2 = (struct HfmNode**)v2;
    // return (*h2)->weight - (*h1)->weight;
    if ((*h2)->weight < (*h1)->weight) {
        return -1;
    } else if ((*h1)->weight < (*h2)->weight) {
        return 1;
    }
    return 0;
}

HfmTree create_hfmtree_by_hfmnodes(struct HfmNode *nodes[], size_t n)
{
    qsort(nodes, n, sizeof(struct HfmNode*), compareWeight); // 节点按权值从大到小排序
    struct HfmNode *root = NULL;
    for (int i = n - 1; i > 0; --i) {
        // 最后一个节点 i>0说明还有节点
        root = (struct HfmNode*)malloc(sizeof(struct HfmNode));
        root->lchild = nodes[i - 1]; // 最小的两个子树合并
        root->rchild = nodes[i];
        root->weight = nodes[i - 1]->weight + nodes[i]->weight;  // 新权值为左右子树之和
        memset(root->code, 0, CODE_LEN);  // 不会用到
        int j;
        for (j = i - 2; j >= 0 && nodes[j]->weight < root->weight; --j) { // 移位让新节点插入
            nodes[j + 1] = nodes[j];
        }
        nodes[j + 1] = root; // 插入新节点
    }
    root = nodes[0];
    return root; // 返回根节点
}
通过哈夫曼树生成哈夫曼编码 hfmcode_hfmtree
void hfmcode_hfmtree(HfmTree tree)  // 左子树为0  右子树为1
{
    if (tree != NULL) {
        if (tree->lchild != NULL) {
            strcpy(tree->lchild->code, tree->code);
            strcat(tree->lchild->code, "0"); // 左边加0
            hfmcode_hfmtree(tree->lchild);
        }
        if (tree->rchild != NULL) {
            strcpy(tree->rchild->code, tree->code);
            strcat(tree->rchild->code, "1"); // 右边加1
            hfmcode_hfmtree(tree->rchild);
        }
    }
}

文件压缩和解压功能函数

// 通过文件建立哈夫曼树
HfmTree build_hfmtree_by_file(const char *file, const char *dfile, struct HfmCompress *phfm);

// 把哈夫曼编码转换成二进制
unsigned char code_to_byte(const char *codes, int len);

// 压缩成哈夫曼编码
static int compress_by_hfmcode(const char *srcFile, const char *destFile,
                               HfmTree tree, struct HfmNode *index[]);

// 获取压缩文件中的哈夫曼节点 并且 建立哈夫曼树 返回哈夫曼树根节点
HfmTree get_hfmtree_by_compress_file(FILE *fp, size_t *fs);

// 把哈夫曼编码转换成原字符
int code_to_char(const char *codes, unsigned char *byte, HfmTree tree);

// 解压 哈夫曼编码->字符
int dencompress_file(FILE *fr, FILE *fw, HfmTree tree, size_t fs);

// 压缩
int compress(const char *srcFile, const char *destFile);

// 解压
int dencompress(const char *srcFile, const char *destFile);

压缩解压实现

通过文件建立哈夫曼树 build_hfmtree_by_file
void EWC(unsigned char e, size_t w, char *code)
{
    printf("%c     %2d     %s\n", e, w, code);
}

// 通过文件建立哈夫曼树
HfmTree build_hfmtree_by_file(const char *file, const char *dfile, struct HfmCompress *phfm)
{
    // binary 二进制模式     text 文本模式
    // 在linux下面binary和text没有任何区别
    // 在windows下面用text模式'\n'进行处理  程序中写入文件'\n'  实际写入\r\n
    FILE *fp = fopen(file, "rb");
    if (fp == NULL) {
        return NULL;
    }
    unsigned char buf[BUFF_LEN] = {};
    size_t cnt = 0;
    while ((cnt  = fread(buf, 1, BUFF_LEN, fp)) > 0) { // 统计每个字符出现次数(建立哈夫曼节点)
        // 从文件中读到了cnt个字节数据
        for (int i = 0; i < cnt; i++) {
            unsigned char b = buf[i];
            if (phfm->index[b] == NULL) {  // 第一次出现申请一个结点
                struct HfmNode *node = (struct HfmNode*)malloc(sizeof(struct HfmNode));
                node->lchild = node->rchild = NULL;
                node->data = b;
                node->weight = 1;
                // indext数组,为了通过字符b找到对应的哈夫曼结点
                phfm->index[b] = node;
                // nodes数组用于创建哈夫曼树   排序
                phfm->nodes[phfm->len] = node;
                ++phfm->len;
            } else {  // 字符b已经出现过,累加权值
                ++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 (int 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);
    HfmTree tree = create_hfmtree_by_hfmnodes(phfm->nodes, phfm->len); // 通过节点构建哈夫曼树
    hfmcode_hfmtree(tree); // 生成哈夫曼编码
    foreach_hfmtree(tree, EWC);
    return tree;
}
把哈夫曼编码转换成二进制 code_to_byte
unsigned char code_to_byte(const char *codes, int len)
{    // 每7个哈夫曼编码转换为一个字节,防止出现文件结束符EOF(1111 1111)
    unsigned char byte = 0;
    int i;
    for (i = 0; i < len && i < 7; ++i) {
        byte = (byte << 1) | (codes[i] - '0');
    }
    for (; i < 7; ++i) {
        byte = (byte << 1);
    }
    return byte;
}
压缩成哈夫曼编码 compress_by_hfmcode
static int compress_by_hfmcode(const char *srcFile, const char *destFile,
                               HfmTree tree, struct HfmNode *index[])
{
    FILE *fr = fopen(srcFile, "rb");
    if (fr == NULL) return FAILURE;
    FILE *fw = fopen(destFile, "ab");  // 在文件后面添加  前面存储了哈夫曼节点
    if (fw == NULL) {
        fclose(fr);
        return FAILURE;
    }
    unsigned char buf[BUFF_LEN] = {};
    size_t cnt = 0;
    // 一个字符  CODE_LEN  哈夫曼编码的字符串长度 BUFF_LEN * CODE_LEN
    unsigned char codes[BUFF_LEN * CODE_LEN] = {};
    int codeLen = 0;
    while ((cnt = fread(buf, 1, BUFF_LEN, fr)) > 0) { // 循环读取文本内容
        for (int i = 0; i < cnt; ++i) { 
            unsigned char b = buf[i];
            // index[b]->code;
            // strcat(codes, index[b]->code);
            strcpy(codes + codeLen, index[b]->code); // 把每个字符转换为哈夫曼编码,累加起来
            codeLen += strlen(index[b]->code); // 记录哈夫曼编码总长度
        }
        // 哈夫曼编码字符串codes "01010111001100" ----> 转换成二进制
        unsigned char res[BUFF_LEN] = {};
        size_t n = 0;
        while(codeLen >= 7) { // 每7个哈夫曼编码转化为一个字节,首位为0
            res[n] = code_to_byte(codes + 7 * n, codeLen);
            codeLen -= 7;
            ++n;
        }
        fwrite(res, 1, n, fw);
        if (codeLen > 0) { // 不够7个 留到下次
            strncpy(codes, codes + 7 * n, codeLen);
            codes[codeLen] = '\0';
        }
    }
    if (codeLen > 0) { // 补齐7位
        unsigned char byte = code_to_byte(codes, codeLen);
        fwrite(&byte, 1, 1, fw);
    }
    fclose(fr);
    fclose(fw);
    return SUCCESS;
}
获取压缩文件中的哈夫曼节点 并且 建立哈夫曼树 返回哈夫曼树根节点get_hfmtree_by_compress_file
HfmTree get_hfmtree_by_compress_file(FILE *fp, size_t *fs)
{
    fread(fs, sizeof(*fs), 1, fp); // 读取原始文件大小
    size_t n = 0;
    fread(&n, sizeof(n), 1, fp); // 读取原始文件中元素个数
    unsigned char datas[n];
    size_t weights[n];
    size_t i;
    for (int i = 0; i < n; ++i) {
        fread(&datas[i], sizeof(datas[i]), 1, fp); // 一个字符
        fread(&weights[i], sizeof(weights[i]), 1, fp); // 字符对应的权值
    }
    return create_hfmtree(datas, weights, n); // 建立哈夫曼树
}
把哈夫曼编码转换成原字符 code_to_char
int code_to_char(const char *codes, unsigned char *byte, HfmTree tree)
{
    int i;
    struct HfmNode *node = tree;
    for (i = 0; codes[i] != '\0'; ++i) {
        if (codes[i] == '0') { // 如果为0向左走
            node = node->lchild;
        } else {     // 如果为1向右走
            node = node->rchild;
        }
        if (node->lchild == NULL && node->rchild == NULL) { // 哈夫曼树叶子节点
            *byte = node->data;
            return i + 1;  // 返回用了几个哈夫曼编码
        }
    }
    return 0; // 没找到返回0
}
解压 哈夫曼编码->字符 dencompress_file
int dencompress_file(FILE *fr, FILE *fw, HfmTree tree, size_t fs)
{
    unsigned char buf[BUFF_LEN] = {};
    size_t cnt = 0; // 记录从文件中读取到的字节数
    unsigned char codes[BUFF_LEN * 8 + 1] = {};
    size_t codeLen = 0; // 记录转换成哈夫曼编码的长度
    int i, j;
    size_t sum = 0; // 记录写入文件的字节数 不能大于fs
    // 记录现在的读取位置 pos
    // 再调到文件末尾获取大小
    // 再读文件过程中,如果提前中断,说明读到了(end of file)1111 1111
    // int res = 0;
    while ((cnt = fread(buf, 1, BUFF_LEN, fr)) > 0) {
        // res += cnt;
        for (i = 0; i < cnt; ++i) {
            for (j = 6; j >= 0; --j) { // 一个字节中存储了7个哈夫曼编码
                codes[codeLen] = ((buf[i] >> j) & 0x1) + '0';
                ++codeLen;
            }
        }
        codes[codeLen] = '\0'; // 避免段错误
        // printf("%ld\n", ftell(fr));
        unsigned char res[BUFF_LEN * 8 + 1] = {};
        size_t resLen = 0;
        size_t n = 0;
        size_t len = 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';
        }
    }
    // printf("%s\n", codes);
    fclose(fr);
    fclose(fw);
}
压缩 compress
// 对srcFile进行压缩   // 压缩之后的文件名为 destFile
int compress(const char *srcFile, const char *destFile)
{
    assert(srcFile != NULL && destFile != NULL);
    if (strcmp(srcFile, destFile) == 0) {
        return FAILURE;
    }
    struct HfmCompress hfm = {}; // 两个指针数组全为NULL
    HfmTree tree = build_hfmtree_by_file(srcFile, destFile, &hfm); // 通过文件构建哈夫曼树
    if (tree == NULL) {
        return FAILURE;
    }
    int ret = compress_by_hfmcode(srcFile, destFile, tree, hfm.index); // 压缩
    destroy_hfmtree(tree);
    return ret;
}
解压 dencompress
int dencompress(const char *srcFile, const char *destFile)
{
    assert(srcFile != NULL && destFile != NULL);
    FILE *fr = fopen(srcFile, "rb");
    if (fr == NULL) {
        return FAILURE;
    }
    FILE *fw = fopen(destFile, "wb");
    if (fw == NULL) {
        fclose(fr);
        return FAILURE;
    }
    size_t fs = 0;
    HfmTree tree = get_hfmtree_by_compress_file(fr, &fs); // 从文件获取哈夫曼树
    hfmcode_hfmtree(tree); // 生成哈夫曼编码
    int ret = dencompress_file(fr, fw, tree, fs); // 解压
    destroy_hfmtree(tree);
    return ret;
    // foreach_hfmtree(tree, EWC);
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
哈夫曼树是一种特殊的二叉树结构,用于编码和解码数据。在哈夫曼树中,每个叶子节点都代表一个字符或符号,并且具有一个与之关联的权值,代表该字符或符号出现的频率或概率。根据哈夫曼树的概念,我们可以通过给定的叶子节点的权值来构建哈夫曼树。 对于给定的叶子节点的权值,构建哈夫曼树的步骤如下: 1. 首先,根据叶子节点的权值从小到大进行排序。 2. 选取权值最小的两个叶子节点,并将它们作为两个子节点创建一个新的父节点。新父节点的权值等于这两个子节点的权值之和。 3. 将这个新的父节点插入到叶子节点中,同时删除原来的两个子节点。 4. 重复步骤2和步骤3,直到只剩下一个节点,即根节点,这个节点就是哈夫曼树的根节点。 根据题目提供的例子,我们可以看到一种不是建树的方法,只使用数组来模拟哈夫曼树的构造过程。这种方法是通过数组来存储节点的信息,并通过一些特定的计算方式来模拟构建哈夫曼树的过程。 根据题目的描述,我们需要根据叶子节点的个数和权值来生成哈夫曼树,并计算所有节点的值与权值的乘积之和。这个问题可以通过构建哈夫曼树的步骤来解决。首先,我们需要将叶子节点根据权值进行排序。然后,按照步骤2和步骤3构建哈夫曼树,直到只剩下一个节点。最后,计算所有节点的值与权值的乘积之和。 综上所述,数据结构哈夫曼树的例题是通过给定叶子节点的权值来构建哈夫曼树,并计算所有节点的值与权值的乘积之和。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [超好理解的哈夫曼树(最优二叉树)与例题](https://blog.csdn.net/weixin_45720782/article/details/109316157)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值