哈夫曼树 C语言实现

原创 2013年12月06日 23:10:03

1、基本概念


a、路径和路径长度

若在一棵树中存在着一个结点序列 k1,k2,……,kj, 使得 kiki+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)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。


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


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


具体算法如下:

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

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

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

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

a 的编码为:00

b 的编码为:01

c 的编码为:100

d 的编码为:1010

e 的编码为:1011

f 的编码为:11


4、哈夫曼树的操作运算


以上文的哈夫曼树作为具体实例,用详细的程序展示哈夫曼树的操作运算

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

运行结果:




相关文章推荐

哈夫曼树

点击打开链接 哈夫曼树,第一行输入一个数n,表示叶结点的个数。需要用这些叶结点生成哈夫曼树,根据哈夫曼树的概念,这些结点有权值,即weight,题目需要输出所有结点的值与权值的乘积之和。 ...

哈夫曼树

一、哈夫曼树的概念和定义   什么是哈夫曼树? 让我们先举一个例子。 判定树:         在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例...

哈夫曼树

一、定义 Huffman树,又称为最优二叉树,是加权路径长度最短的二叉树。 简单的来说就是权值越大路径越短(权值),这样整体最优(权值*路径长度的和最小) 二、证明huffman编码是最优编码 设某个...

哈夫曼树与哈夫曼编码

哈夫曼树与哈夫曼编码 在一般的数据结构的书中,树的那章后面,著者一般都会介绍一下哈夫曼(HUFFMAN) 树和哈夫曼编码。哈夫曼编码是哈夫曼树的一个应用。哈夫曼编码应用广泛,如 JPEG中就应用...

哈夫曼树的一些操作

//      哈夫曼树 //    完成以下功能。 //     1.初始化模块.从键盘输入N个字符及N个权值,建立哈夫曼树。 //     2.编码功能.利用建立的哈夫曼树,对一段正文进行编...

哈夫曼树

介绍 哈夫曼树,英文名 Huffman Tree, 又称赫夫曼树或最优二叉树。其主要用途是在于解决哈夫曼编码。哈夫曼编码则目的是在字节长度一定下,尽可能传输较多的信息。 这也就是要让出现在明文中次数较...

哈夫曼树的基本构建与操作

看到的讲解huffman树的一篇比较好懂的博客,唯一不足的地方就是代码里面使用Malloc时没有强制类型转换。。。。 出处:http://blog.csdn.net/wtfmonking/articl...

哈夫曼树 C语言实现

哈夫曼树 C语言实现 分类: Data structure & Algorithm2013-12-06 23:10 180人阅读 评论(0) 收藏 举报 哈夫曼树哈夫曼编码 目录(?)[...

二叉树的应用--哈夫曼树

哈夫曼树,最优二叉树。

哈夫曼树详解

回答问题之前先来看看什么是压缩。当你有天走在路上,碰见熟人对你说:“吃了?”你一定知道他是在打招呼,既不是要请客也不是让你“没吃赶紧回家吃去”。这一句简单的“吃了”是礼貌和问好的体现,也是一种信息的压...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:哈夫曼树 C语言实现
举报原因:
原因补充:

(最多只允许输入30个字)