数据结构—哈夫曼树与哈夫曼编码

一,什么是哈夫曼树

哈夫曼树是一种带权路径长度最短的二叉树,也称为最优二叉树。下面用一幅图来说明。

ds48

它们的带权路径长度分别为:

图a: WPL=5*2+7*2+2*2+13*2=54

图b: WPL=5*3+2*3+7*2+13*1=48

可见,图b的带权路径长度较小,我们可以证明图b就是哈夫曼树(也称为最优二叉树)。

二,如何构建哈夫曼树

一般可以按下面步骤构建:

1,将所有左,右子树都为空的作为根节点。

2,在森林中选出两棵根节点的权值最小的树作为一棵新树的左,右子树,且置新树的附加根节点的权值为其左,右子树上根节点的权值之和。注意,左子树的权值应小于右子树的权值。

3,从森林中删除这两棵树,同时把新树加入到森林中。

4,重复2,3步骤,直到森林中只有一棵树为止,此树便是哈夫曼树。

下面是构建哈夫曼树的图解过程:

ds52

三,哈夫曼编码

利用哈夫曼树求得的用于通信的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。

就拿上图例子来说:

A,B,C,D对应的哈夫曼编码分别为:111,10,110,0

用图说明如下:

ds50

记住,设计电文总长最短的二进制前缀编码,就是以n个字符出现的频率作为权构造一棵哈夫曼树,由哈夫曼树求得的编码就是哈夫曼编码。

#include<stdio.h>  
#include<stdlib.h>  
typedef int ElemType;  
struct BTreeNode  
{  
    ElemType data;  
    struct BTreeNode* left;  
    struct BTreeNode* right;  
};  
  
//1、输出二叉树,可在前序遍历的基础上修改。采用广义表格式,元素类型为int  
void PrintBTree_int(struct BTreeNode* BT)  
{  
    if (BT != NULL)  
    {  
        printf("%d", BT->data); //输出根结点的值  
        if (BT->left != NULL || BT->right != NULL)  
        {  
            printf("(");  
            PrintBTree_int(BT->left); //输出左子树  
            if (BT->right != NULL)  
                printf(",");  
            PrintBTree_int(BT->right); //输出右子树  
            printf(")");  
        }  
    }  
}  
  
//2、根据数组 a 中 n 个权值建立一棵哈夫曼树,返回树根指针  
struct BTreeNode* CreateHuffman(ElemType a[], int n)  
{  
    int i, j;  
    struct BTreeNode **b, *q;  
    b = malloc(n*sizeof(struct BTreeNode));  
    for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点  
    {  
        b[i] = malloc(sizeof(struct BTreeNode));  
        b[i]->data = a[i];  
        b[i]->left = b[i]->right = NULL;  
    }  
    for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树  
    {  
        //k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标  
        int k1 = -1, k2;  
        for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵  
        {  
            if (b[j] != NULL && k1 == -1)  
            {  
                k1 = j;  
                continue;  
            }  
            if (b[j] != NULL)  
            {  
                k2 = j;  
                break;  
            }  
        }  
        for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小  
        {  
            if (b[j] != NULL)  
            {  
                if (b[j]->data < b[k1]->data)  
                {  
                    k2 = k1;  
                    k1 = j;  
                }  
                else if (b[j]->data < b[k2]->data)  
                    k2 = j;  
            }  
        }  
        //由最小权值树和次最小权值树建立一棵新树,q指向树根结点  
        q = malloc(sizeof(struct BTreeNode));  
        q->data = b[k1]->data + b[k2]->data;  
        q->left = b[k1];  
        q->right = b[k2];  
  
        b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置  
        b[k2] = NULL;//k2位置为空  
    }  
    free(b); //删除动态建立的数组b  
    return q; //返回整个哈夫曼树的树根指针  
}  
  
//3、求哈夫曼树的带权路径长度  
ElemType WeightPathLength(struct BTreeNode* FBT, int len)//len初始为0  
{  
    if (FBT == NULL) //空树返回0  
        return 0;  
    else  
    {  
        if (FBT->left == NULL && FBT->right == NULL)//访问到叶子结点  
            return FBT->data * len;  
        else //访问到非叶子结点,进行递归调用,返回左右子树的带权路径长度之和,len递增  
            return WeightPathLength(FBT->left,len+1)+WeightPathLength(FBT->right,len+1);  
    }  
}  
  
//4、哈夫曼编码(可以根据哈夫曼树带权路径长度的算法基础上进行修改)  
void HuffManCoding(struct BTreeNode* FBT, int len)//len初始值为0  
{  
    static int a[10];//定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一  
    if (FBT != NULL)//访问到叶子结点时输出其保存在数组a中的0和1序列编码  
    {  
        if (FBT->left == NULL && FBT->right == NULL)  
        {  
            int i;  
            printf("结点权值为%d的编码:", FBT->data);  
            for (i = 0; i < len; i++)  
                printf("%d", a[i]);  
            printf("\n");  
        }  
        else//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a  
        {   //的对应元素中,向下深入一层时len值增1  
            a[len] = 0;  
            HuffManCoding(FBT->left, len + 1);  
            a[len] = 1;  
            HuffManCoding(FBT->right, len + 1);  
        }  
    }  
}  
  
//主函数  
void main()  
{  
    int n, i;  
    ElemType* a;  
    struct BTreeNode* fbt;  
    printf("从键盘输入待构造的哈夫曼树中带权叶子结点数n:");  
    while(1)  
    {  
        scanf("%d", &n);  
        if (n > 1)  
            break;  
        else  
            printf("重输n值:");  
    }  
    a = malloc(n*sizeof(ElemType));  
    printf("从键盘输入%d个整数作为权值:", n);  
    for (i = 0; i < n; i++)  
        scanf(" %d", &a[i]);  
    fbt = CreateHuffman(a, n);  
    printf("广义表形式的哈夫曼树:");  
    PrintBTree_int(fbt);  
    printf("\n");  
    printf("哈夫曼树的带权路径长度:");  
    printf("%d\n", WeightPathLength(fbt, 0));  
    printf("树中每个叶子结点的哈夫曼编码:\n");  
    HuffManCoding(fbt, 0);  
}  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值