数据结构之霍夫曼树

本文介绍了霍夫曼树的基本概念,包括路径长度、带权路径长度等,并详细阐述了如何构造霍夫曼树的过程。同时,文章还讲解了霍夫曼编码的生成规则,通过从根节点到叶子节点的路径确定字符的编码。最后,提到了测试代码用于实现相关功能。

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

1.相关概念
霍夫曼树是所有与树有关的结构中最优美的,也叫做哈夫曼树。在学习霍夫曼树之前必须了解几个概念:

①路径:从树中的一个节点到另一个节点之间的分支结构通路
②路径长度:路径上的分支数目
③树的路径长度:从树根到每一个节点的路径长度之和
④节点的带权路径长度:从该节点开始到树根之间的路径长度与节点上的权重的乘积
⑤树的带权路径长度:是树中所有叶子结点的带权路径长度的和

所谓的霍夫曼树,假设有 n 个权值{a1,a2,…,an},可以构造出一棵具有 n 个叶子节点的二叉树,每个叶子结点的权值为ni,则其中的带权路径长度最短的二叉树称作霍夫曼树,也叫最优二叉树。

2.构造霍夫曼树的过程
①:将 n 个给定的权值作为根节点创建n个树,{T1,T2,…,Tn},即组成了一个森林
②:然后,从上面的森林中选择两棵权值最小的二叉树作为左右子节点合并成一个二叉树,新的二叉树的权值为两个子树的权值之和
③:剔除上面的两个子树,将新生成的二叉树加入森林中
④:重复②和③,直至森林中只有一棵二叉树为止

3.霍夫曼编码
以字符出现的频率为权构造霍夫曼树,在霍夫曼树中从根节点开始往左子树走记为0,往右子树走记为1,知道走到叶子结点,则得到的0,1 符号串即为该字符的霍夫曼编码

④.基本代码


#include <stdint.h>
#include <afxres.h>

typedef struct {
    int weight; //权值 
    int parent; //父结点序号 
    int left; //左子树序号
    int right; //右子树序号 
} HuffmanTree;

typedef char *HuffmanCode;  //Huffman编码 

void SelectNode(HuffmanTree *ht, int n, int *bt1, int *bt2)
//从1~x个结点选择parent结点为0,权重最小的两个结点 
{
    int i;
    HuffmanTree *ht1, *ht2, *t;
    ht1 = ht2 = NULL; //初始化两个结点为空
    for (i = 1; i <= n; ++i) //循环处理1~n个结点(包括叶结点和非叶结点)
    {
        if (!ht[i].parent) //父结点为空(结点的parent=0)
        {
            if (ht1 == NULL) //结点指针1为空
            {
                ht1 = ht + i; //指向第i个结点
                continue; //继续循环
            }
            if (ht2 == NULL) //结点指针2为空
            {
                ht2 = ht + i; //指向第i个结点
                if (ht1->weight > ht2->weight) //比较两个结点的权重,使ht1指向的结点权重小
                {
                    t = ht2;
                    ht2 = ht1;
                    ht1 = t;
                }
                continue; //继续循环
            }
            if (ht1 && ht2) //若ht1、ht2两个指针都有效
            {
                if (ht[i].weight <= ht1->weight) //第i个结点权重小于ht1指向的结点
                {
                    ht2 = ht1; //ht2保存ht1,因为这时ht1指向的结点成为第2小的
                    ht1 = ht + i; //ht1指向第i个结点
                } else if (ht[i].weight < ht2->weight) { //若第i个结点权重小于ht2指向的结点
                    ht2 = ht + i; //ht2指向第i个结点
                }
            }
        }
    }
    if (ht1 > ht2) { //增加比较,使二叉树左侧为叶结点
        *bt2 = ht1 - ht;
        *bt1 = ht2 - ht;
    } else {
        *bt1 = ht1 - ht;
        *bt2 = ht2 - ht;
    }
}

void CreateTree(HuffmanTree *ht, int n, int *w) {
    int i, m = 2 * n - 1;//总的节点数
    int bt1, bt2; //二叉树结点序与
    if (n <= 1) return; //只有一个结点,无法创建
    for (i = 1; i <= n; ++i) //初始化叶结点
    {
        ht[i].weight = w[i - 1];
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    for (; i <= m; ++i)//初始化后续结点
    {
        ht[i].weight = 0;
        ht[i].parent = 0;
        ht[i].left = 0;
        ht[i].right = 0;
    }
    for (i = n + 1; i <= m; ++i) //逐个计算非叶结点,创建Huffman树
    {
        SelectNode(ht, i - 1, &bt1, &bt2); //从1~i-1个结点选择parent结点为0,权重最小的两个结点
        ht[bt1].parent = i;
        ht[bt2].parent = i;
        ht[i].left = bt1;
        ht[i].right = bt2;
        ht[i].weight = ht[bt1].weight + ht[bt2].weight;
    }
}

void HuffmanCoding(HuffmanTree *ht, int n, HuffmanCode *hc) {
    char *cd;
    int start, i;
    int current, parent;
    cd = (char *) malloc(sizeof(char) * n);//用来临时存放一个字符的编码结果
    cd[n - 1] = '\0'; //设置字符串结束标志
    for (i = 1; i <= n; i++) {
        start = n - 1;
        current = i;
        parent = ht[current].parent;//获取当前结点的父结点
        while (parent) //父结点不为空
        {
            if (current == ht[parent].left)//若该结点是父结点的左子树
                cd[--start] = '0'; //编码为0
            else //若结点是父结点的右子树
                cd[--start] = '1'; //编码为1
            current = parent; //设置当前结点指向父结点
            parent = ht[parent].parent; //获取当前结点的父结点序号
        }
        hc[i - 1] = (char *) malloc(sizeof(char) * (n - start));//分配保存编码的内存
        strcpy(hc[i - 1], &cd[start]); //复制生成的编码
    }
    free(cd); //释放编码占用的内存
}

void Encode(HuffmanCode *hc, char *alphabet, char *str, char *code)
//将一个字符串转换为Huffman编码
//hc为Huffman编码表 ,alphabet为对应的字母表,str为需要转换的字符串,code返回转换的结果 
{

    int len = 0, i = 0, j;
    code[0] = '\0';
    while (str[i]) {
        j = 0;
        while (alphabet[j] != str[i])
            j++;
        strcpy(code + len, hc[j]); //将对应字母的Huffman编码复制到code指定位置
        len = len + strlen(hc[j]); //累加字符串长度
        i++;
    }
    code[len] = '\0';
}

void Decode(HuffmanTree *ht, int m, char *code, char *alphabet, char *decode)
//将一个Huffman编码组成的字符串转换为明文字符串 
//ht为Huffman二叉树,m为字符数量,alphabet为对应的字母表,str为需要转换的字符串,decode返回转换的结果 
{
    int position = 0, i, j = 0;
    m = 2 * m - 1;
    while (code[position]) //字符串未结束
    {
        for (i = m; ht[i].left && ht[i].right; position++) //在Huffman树中查找左右子树为空 ,以构造一个Huffman编码
        {
            if (code[position] == '0') //编码位为0
                i = ht[i].left; //处理左子树
            else //编译位为 1
                i = ht[i].right; //处理右子树
        }
        decode[j] = alphabet[i - 1]; //得到一个字母
        j++;//处理下一字符
    }
    decode[j] = '\0'; //字符串结尾
}

测试代码

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Huffman.c"
int main()
{
    int i,n=4,m; 
    char test[]="DBDBDABDCDADBDADBDADACDBDBD";
    char code[100],code1[100];
    char alphabet[]={'A','B','C','D'}; //4个字符
    int w[]={5,7,2,13} ;//4个字符的权重 
    HuffmanTree *ht;
    HuffmanCode *hc;    
    m=2*n-1;    
    ht=(HuffmanTree *)malloc((m+1)*sizeof(HuffmanTree)); //申请内存,保存赫夫曼树 
    if(!ht)
    {
        printf("内存分配失败!\n");
        exit(0);    
    }
    hc=(HuffmanCode *)malloc(n*sizeof(char*));
    if(!hc)
    {
        printf("内存分配失败!\n");
        exit(0);    
    }

    CreateTree(ht,n,w); //创建赫夫曼树 
    HuffmanCoding(ht,n,hc); //根据赫夫曼树生成赫夫曼编码 
    for(i=1;i<=n;i++) //循环输出赫夫曼编码 
        printf("字母:%c,权重:%d,编码为 %s\n",alphabet[i-1],ht[i].weight,hc[i-1]);

    Encode(hc,alphabet,test,code); //根据赫夫曼编码生成编码字符串 
    printf("\n字符串:\n%s\n转换后为:\n%s\n",test,code); 

    Decode(ht,n,code,alphabet,code1); //根据编码字符串生成解码后的字符串 
    printf("\n编码:\n%s\n转换后为:\n%s\n",code,code1); 
    getch();
    return 0;
}

霍夫曼树和霍夫曼编码是一种常用的数据压缩算法,以下是用C语言实现霍夫曼树和霍夫曼编码的代码。 首先,定义霍夫曼树的节点结构体: ```c typedef struct node { int weight; // 权重 int parent, lchild, rchild; // 父节点、左右孩子节点的下标 }HuffmanNode, *HuffmanTree; ``` 定义霍夫曼编码的结构体: ```c typedef struct { int bit[MaxBit]; // 存放霍夫曼编码的数组 int start; // 编码的起始位置 }HuffmanCode; ``` 接下来,定义霍夫曼树的创建函数: ```c void createHuffmanTree(HuffmanTree ht, int n) { int i, j, min1, min2; // 初始化 for (i = 0; i < 2 * n - 1; i++) { ht[i].parent = ht[i].lchild = ht[i].rchild = -1; } // 输入权值 for (i = 0; i < n; i++) { scanf("%d", &ht[i].weight); } // 构造霍夫曼树 for (i = n; i < 2 * n - 1; i++) { min1 = min2 = MaxValue; ht[i].weight = 0; for (j = 0; j < i; j++) { if (ht[j].parent == -1) { if (ht[j].weight < min1) { min2 = min1; min1 = ht[j].weight; ht[i].lchild = j; } else if (ht[j].weight < min2) { min2 = ht[j].weight; ht[i].rchild = j; } } } ht[i].weight = ht[ht[i].lchild].weight + ht[ht[i].rchild].weight; ht[ht[i].lchild].parent = i; ht[ht[i].rchild].parent = i; } } ``` 接下来,定义霍夫曼编码的生成函数: ```c void getHuffmanCode(HuffmanTree ht, HuffmanCode* hc, int n) { int i, j, c, p; HuffmanCode hc_tmp; for (i = 0; i < n; i++) { hc[i].start = n; c = i; p = ht[c].parent; while (p != -1) { if (ht[p].lchild == c) { hc_tmp.bit[hc_tmp.start--] = 0; } else { hc_tmp.bit[hc_tmp.start--] = 1; } c = p; p = ht[c].parent; } hc[i].start = hc_tmp.start + 1; for (j = hc[i].start; j < n; j++) { hc[i].bit[j] = hc_tmp.bit[j]; } } } ``` 最后,可以在主函数中调用以上函数来实现霍夫曼树和霍夫曼编码的创建: ```c int main() { int n; scanf("%d", &n); HuffmanTree ht = (HuffmanTree)malloc(sizeof(HuffmanNode) * 2 * n - 1); HuffmanCode* hc = (HuffmanCode*)malloc(sizeof(HuffmanCode) * n); createHuffmanTree(ht, n); getHuffmanCode(ht, hc, n); // 输出霍夫曼编码 for (int i = 0; i < n; i++) { printf("%d: ", ht[i].weight); for (int j = hc[i].start; j < n; j++) { printf("%d", hc[i].bit[j]); } printf("\n"); } return 0; } ``` 这样,就可以实现霍夫曼树和霍夫曼编码的创建了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值