哈夫曼树代码实现

本文详细介绍了哈夫曼树的概念,包括结点的权值和带权路径长度的定义,以及如何通过构造过程生成哈夫曼树,计算其带权路径长度,并实现固定长度编码和哈夫曼编码。提供了完整的代码示例和程序运行截图。
摘要由CSDN通过智能技术生成

哈夫曼树的相关概念

结点的权值: 某种特定含义的数值
结点的带权路径长度: 根到结点路径长度×结点的权值
树的带权路径长度: 所有叶子结点的带权路径长度之和
哈夫曼树: 在给定叶子结点权值的二叉树中带权路径长度最小的二叉树
固定长度编码: 在数据通信中,对每个字符用相等长度的二进制位表示的编码方式
可变长度编码: 对频率高的字符短编码,对频率低的字符长编码从而使平均编码长度减短的编码方式
哈夫曼编码: 由哈夫曼树得到的一种被广泛应用且非常有效的数据压缩编码

哈夫曼树的存储结构

采用二叉树的顺序存储方式来存储哈夫曼树。
代码

//哈夫曼树的结点
typedef struct HTNode {
    int weight; //权值
    int parent; //双亲下标
    int lChild; //左孩子下标
    int rChild; //右孩子下标
} HTNode;

//哈夫曼树
typedef struct HT {
    int numLeaf; //叶子结点树
    int WPL; //带权路径长度
    char **code; //哈夫曼编码
    HTNode *node; //树的结点
} HT;

初始化哈夫曼树

过程
给定5个结点权值分别为1,3,2,7,2,初始化后数据结构如下表:

结点下标结点权值父结点下标左孩子下标右孩子下标
01-1-1-1
13-1-1-1
22-1-1-1
37-1-1-1
42-1-1-1

代码

//初始化哈夫曼树
void initHT(HT *ht) {
    printf("叶子结点数:");
    scanf("%d", &ht->numLeaf); //获取叶子结点树
    ht->node = (HTNode *) malloc(sizeof(HTNode) * (2 * ht->numLeaf - 1)); //哈夫曼树总结点数始终为叶子结点数的两倍减一
    for (int i = 0; i < ht->numLeaf; i++) {
        //循环给每个叶子结点赋予权值
        printf("第%d个叶子结点权值:", i + 1);
        scanf("%d", &ht->node[i].weight); //给第i个叶子结点赋予权值
        ht->node[i].parent = ht->node[i].lChild = ht->node[i].rChild = -1; //将所有叶子结点的双亲下标初始化为-1
    }
    ht->code = (char **) malloc(sizeof(char *) * ht->numLeaf); //存储每一个叶子结点的哈夫曼编码
    for (int i = 0; i < ht->numLeaf; i++) {
        //初始化哈夫曼编码数组
        ht->code[i] = (char *) malloc(sizeof(char) * ht->numLeaf); //哈夫曼编码不会超过叶子结点数量减一最后一位存'\0'
        memset(ht->code[i], '\0', sizeof(char *)); //给每个字符串附上初值
    }
    ht->WPL = 0; //初始化带权路径长度
}

构造哈夫曼树

过程
哈夫曼树的构造过程
哈夫曼树构建完成后上述例子的数据结构如下表:

结点下标结点权值父结点下标左孩子下标右孩子下标
015-1-1
136-1-1
225-1-1
378-1-1
426-1-1
53702
65741
78856
815-137

代码

//构造哈夫曼树
void createHT(const HT *ht) {
    for (int i = 0; i < ht->numLeaf - 1; i++) {
        //一共要合并叶子结点数减一次
        int minNode1 = -1, minNode2 = -1, minWeight1 = INT_MAX, minWeight2 = INT_MAX; //记录没有双亲的最小的两个结点的下标和权值
        for (int j = 0; j < ht->numLeaf + i; j++) {
            //合并后的新结点也要参与筛选
            if (ht->node[j].weight < minWeight2 && ht->node[j].parent == -1) {
                minWeight2 = ht->node[j].weight;
                minNode2 = j;
                if (minWeight1 > minWeight2) {
                    //如果之前的权值比新选的大交换它们
                    minWeight2 = minWeight1;
                    minNode2 = minNode1;
                    minWeight1 = ht->node[j].weight;
                    minNode1 = j;
                }
            }
        }
        ht->node[minNode1].parent = ht->node[minNode2].parent = ht->numLeaf + i; //确定双亲结点的下标
        ht->node[ht->numLeaf + i].weight = minWeight1 + minWeight2; //给双亲结点赋予权值
        ht->node[ht->numLeaf + i].parent = -1; //将双亲结点的双亲结点下标初始化为-1
        ht->node[ht->numLeaf + i].lChild = minNode1; //确定双亲结点的左孩子下标
        ht->node[ht->numLeaf + i].rChild = minNode2; //确定双亲结点的右孩子下标
    }
}

计算带权路径长度

过程
带权路径长度等于所有叶子结点的带权路径长度之和
计算带权路径长度
W P L = 1 × 80 + 2 × 10 + 3 × ( 2 + 8 ) = 130 WPL=1×80+2×10+3×(2+8)=130 WPL=1×80+2×10+3×(2+8)=130

代码

//计算带权路径长度
void calculateWPL(HT *ht) {
    for (int i = 0; i < ht->numLeaf; i++) {
        //从每个叶子结点开始寻找根结点
        const HTNode *p = &ht->node[i]; //依次记录每个叶子结点地址
        int length = 0; //记录叶子结点到根结点的路径长度
        while (p->parent != -1) {
            //没有找到根结点继续向上搜寻
            p = &ht->node[p->parent]; //p指向当前结点的双亲
            length++; //路径长增加
        }
        ht->WPL += ht->node[i].weight * length; //带权路径长度等于所有叶子结点的带权路径长度之和
    }
}

构建哈夫曼树编码

过程
固定长度编码
固定长度编码

结点编码
A00
B01
C10
D11

哈夫曼树编码
哈夫曼树编码

结点编码
A00
B010
C1
D011

对于上述两种编码方式
压缩率 = W P L 哈夫曼树编码 W P L 固定长度编码 × 100 % = 130 200 × 100 % = 65 % 压缩率=\frac{WPL_{哈夫曼树编码}}{WPL_{固定长度编码}} ×100\%= \frac{130}{200} ×100\%=65\% 压缩率=WPL固定长度编码WPL哈夫曼树编码×100%=200130×100%=65%

代码

//构建哈夫曼编码
void createHC(const HT *ht) {
    for (int i = 0; i < ht->numLeaf; i++) {
        //从每个叶子结点开始寻找根结点
        const HTNode *p = &ht->node[i]; //依次记录每个叶子结点地址
        for (int j = 0; p->parent != -1; j++) {
            //没有找到根结点继续向上搜寻
            if (p == &ht->node[ht->node[p->parent].lChild]) {
                ht->code[i][j] = '0'; //如果当前结点是双亲的左孩子编码为0
            } else {
                ht->code[i][j] = '1'; //如果当前结点是双亲的右孩子编码为1
            }
            p = &ht->node[p->parent]; //p指向当前结点的双亲
        }
        strrev(ht->code[i]); //反转字符串
    }
}

完整实现代码

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

//哈夫曼树的结点
typedef struct HTNode {
    int weight; //权值
    int parent; //双亲下标
    int lChild; //左孩子下标
    int rChild; //右孩子下标
} HTNode;

//哈夫曼树
typedef struct HT {
    int numLeaf; //叶子结点树
    int WPL; //带权路径长度
    char **code; //哈夫曼编码
    HTNode *node; //树的结点
} HT;

//初始化哈夫曼树
void initHT(HT *ht) {
    printf("叶子结点数:");
    scanf("%d", &ht->numLeaf); //获取叶子结点树
    ht->node = (HTNode *) malloc(sizeof(HTNode) * (2 * ht->numLeaf - 1)); //哈夫曼树总结点数始终为叶子结点数的两倍减一
    for (int i = 0; i < ht->numLeaf; i++) {
        //循环给每个叶子结点赋予权值
        printf("第%d个叶子结点权值:", i + 1);
        scanf("%d", &ht->node[i].weight); //给第i个叶子结点赋予权值
        ht->node[i].parent = ht->node[i].lChild = ht->node[i].rChild = -1; //将所有叶子结点的双亲下标初始化为-1
    }
    ht->code = (char **) malloc(sizeof(char *) * ht->numLeaf); //存储每一个叶子结点的哈夫曼编码
    for (int i = 0; i < ht->numLeaf; i++) {
        //初始化哈夫曼编码数组
        ht->code[i] = (char *) malloc(sizeof(char) * ht->numLeaf); //哈夫曼编码不会超过叶子结点数量减一最后一位存'\0'
        memset(ht->code[i], '\0', sizeof(char *)); //给每个字符串附上初值
    }
    ht->WPL = 0; //初始化带权路径长度
}

//构造哈夫曼树
void createHT(const HT *ht) {
    for (int i = 0; i < ht->numLeaf - 1; i++) {
        //一共要合并叶子结点数减一次
        int minNode1 = -1, minNode2 = -1, minWeight1 = INT_MAX, minWeight2 = INT_MAX; //记录没有双亲的最小的两个结点的下标和权值
        for (int j = 0; j < ht->numLeaf + i; j++) {
            //合并后的新结点也要参与筛选
            if (ht->node[j].weight < minWeight2 && ht->node[j].parent == -1) {
                minWeight2 = ht->node[j].weight;
                minNode2 = j;
                if (minWeight1 > minWeight2) {
                    //如果之前的权值比新选的大交换它们
                    minWeight2 = minWeight1;
                    minNode2 = minNode1;
                    minWeight1 = ht->node[j].weight;
                    minNode1 = j;
                }
            }
        }
        ht->node[minNode1].parent = ht->node[minNode2].parent = ht->numLeaf + i; //确定双亲结点的下标
        ht->node[ht->numLeaf + i].weight = minWeight1 + minWeight2; //给双亲结点赋予权值
        ht->node[ht->numLeaf + i].parent = -1; //将双亲结点的双亲结点下标初始化为-1
        ht->node[ht->numLeaf + i].lChild = minNode1; //确定双亲结点的左孩子下标
        ht->node[ht->numLeaf + i].rChild = minNode2; //确定双亲结点的右孩子下标
    }
}

//计算带权路径长度
void calculateWPL(HT *ht) {
    for (int i = 0; i < ht->numLeaf; i++) {
        //从每个叶子结点开始寻找根结点
        const HTNode *p = &ht->node[i]; //依次记录每个叶子结点地址
        int length = 0; //记录叶子结点到根结点的路径长度
        while (p->parent != -1) {
            //没有找到根结点继续向上搜寻
            p = &ht->node[p->parent]; //p指向当前结点的双亲
            length++; //路径长增加
        }
        ht->WPL += ht->node[i].weight * length; //带权路径长度等于所有叶子结点的带权路径长度之和
    }
}

//构建哈夫曼编码
void createHC(const HT *ht) {
    for (int i = 0; i < ht->numLeaf; i++) {
        //从每个叶子结点开始寻找根结点
        const HTNode *p = &ht->node[i]; //依次记录每个叶子结点地址
        for (int j = 0; p->parent != -1; j++) {
            //没有找到根结点继续向上搜寻
            if (p == &ht->node[ht->node[p->parent].lChild]) {
                ht->code[i][j] = '0'; //如果当前结点是双亲的左孩子编码为0
            } else {
                ht->code[i][j] = '1'; //如果当前结点是双亲的右孩子编码为1
            }
            p = &ht->node[p->parent]; //p指向当前结点的双亲
        }
        strrev(ht->code[i]); //反转字符串
    }
}

//展示哈夫曼树信息
void showHT(const HT *ht) {
    printf("结点下标    结点权值    双亲下标    左孩子下标  右孩子下标  哈夫曼编码\n");
    for (int i = 0; i < 2 * ht->numLeaf - 1; i++) {
        printf("%-12d%-12d%-12d%-12d%-12d", i, ht->node[i].weight, ht->node[i].parent, ht->node[i].lChild,
               ht->node[i].rChild); //打印哈夫曼树结点的信息
        if (i < ht->numLeaf) {
            printf("%-12s", ht->code[i]); //打印每个叶子结点的哈夫曼编码
        }
        printf("\n");
    }
    printf("带权路径长度WPL=%d\n", ht->WPL); //打印带权路径长度
}

//测试代码
void test() {
    HT ht; //定义哈夫曼树
    initHT(&ht); //初始化哈夫曼树
    createHT(&ht); //构造哈夫曼树
    createHC(&ht); //计算哈夫曼编码
    calculateWPL(&ht); //计算带权路径长度
    showHT(&ht); //展示哈夫曼树信息
}

//主函数
int main() {
    test(); //测试代码
    system("pause"); //暂停
    return 0;
}

程序运行截图

程序运行截图

  • 63
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花园宝宝没有天线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值