哈夫曼树

1、基本概念


a、路径和路径长度

若在一棵树中存在着一个结点序列 k1,k2,……,kj, 使得 ki是ki+1 的双亲(1<=i<j),则称此结点序列是从 k1 到 kj 的路径。

从 k1 到 kj 所经过的分支数称为这两点之间的路径长度,它等于路径上的结点数减1.


b、结点的权和带权路径长度

在许多应用中,常常将树中的结点赋予一个有着某种意义的实数,我们称此实数为该结点的权,(如下面一个树中的蓝色数字表示结点的权)

结点的带权路径长度规定为从树根结点到该结点之间的路径长度与该结点上权的乘积。


c、树的带权路径长度

树的带权路径长度定义为树中所有叶子结点的带权路径长度之和,公式为:


其中,n表示叶子结点的数目,wi 和 li 分别表示叶子结点 ki 的权值和树根结点到 ki 之间的路径长度。

如下图中树的带权路径长度 WPL = 9 x 2 + 12 x 2 + 15 x 2 + 6 x 3 + 3 x 4 + 5 x 4  =  122



d、哈夫曼树

哈夫曼树又称最优二叉树。它是 n 个带权叶子结点构成的所有二叉树中,带权路径长度 WPL 最小的二叉树。

如下图为一哈夫曼树示意图。


2、构造哈夫曼树


假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:


(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);


(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;


(3)从森林中删除选取的两棵树,并将新树加入森林;


(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。


 如:对 下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下:


注意:为了使得到的哈夫曼树的结构尽量唯一,通常规定生成的哈夫曼树中每个结点的左子树根结点的权小于等于右子树根结点的权。


具体算法如下:
[cpp] view plain copy
在CODE上查看代码片派生到我的代码片

    //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、哈夫曼编码

在电报通信中,电文是以二进制的01序列传送的,每个字符对应一个二进制编码,为了缩短电文的总长度,采用不等长编码方式,构造哈夫曼树,

将每个字符的出现频率作为字符结点的权值赋予叶子结点,每个分支结点的左右分支分别用01编码,从树根结点到每个叶子结点的路径上

所经分支的01编码序列等于该叶子结点的二进制编码。如上文所示的哈夫曼编码如下:

a 的编码为:00

b 的编码为:01

c 的编码为:100

d 的编码为:1010

e 的编码为:1011

f 的编码为:11


4、哈夫曼树的操作运算


以上文的哈夫曼树作为具体实例,用详细的程序展示哈夫曼树的操作运算
[cpp] view plain copy
在CODE上查看代码片派生到我的代码片

    #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);  
    }  


运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值