赫夫曼树与赫夫曼编码

赫夫曼树——最优二叉树

书本上百度上均有解释其特点,在此不再一一道来,就由一道题目做索引,直接来谈谈它的构建过程吧额。

赫夫曼编码

时间限制: 1 Sec | 内存限制: 128 MB

输入
输入包含多组数据(不超过100组)
每组数据第一行一个整数n,表示字符个数。接下来n行,每行有一个字符ch和一个整数weight,表示字符ch所对应的权值,中间用空格隔开。
输入数据保证每组测试数据的字符不会重复。
输出
对于每组测试数据,按照输入顺序输出相应的字符以及它们的哈弗曼编码结果,具体格式见样例。

样例输入
3
A 10
B 5
C 8
4
A 1
B 1
C 1
D 1
样例输出
A:0
B:10
C:11
A:00
B:01
C:10
D:11

Huffman树的构建

大致思路 就是一次次的寻找最小权值结点构成一个二叉树,同时父结点将两子结点的权值相加并插入,然后将父结点的权值插入到权值数组里,如此重复的 查找结点、生成二叉树、插入结点,直到数组里只剩下最后一个结点,它就是这棵赫夫曼树的根结点。

构建过程

首先这里有一个权值数组,将其存入到一棵树的叶子结点里
在这里插入图片描述
选出两个权值最小的结点构成一棵简单的二叉树,父结点权值即为两者之子结点之和
在这里插入图片描述
随后将其父结点插入到结点数组里
在这里插入图片描述
再次选择结点,构建二叉树,插入结点, 重复操作,直到只剩下一个结点。
在这里插入图片描述
在这里插入图片描述
如图所示,最终结点就是这棵树的根结点,赫夫曼树构建完毕

当时看完书之后思路很清晰啊,就一头扎进去开始写代码,中间也遇到了不少的错误,最后结合别人的博客文章、视频课程、书本,才将其顺利实现。

构建过程代码如下
//构建哈夫曼树
void CreateHuffmanTree(HuffmanTree &HT, int w[], int n)
{
	//赫夫曼结点数 
    int m = 2 * n - 1;
    //选取两个最小权值的结点
    int s1;
    int s2;
    int i;
    //为树结点分配内存 这里从1忽略了0号元素 
    HT = (HuffmanTree)malloc((m + 1) * sizeof(Node));
	//初始化树叶结点 
    for(i = 1; i <= n; i++)
    {
        HT[i].weight = w[i];
        HT[i].lchild = 0;
        HT[i].parent = 0;
        HT[i].rchild = 0;
    }
    //非叶子结点的初始化 
    for(i = n + 1; i <= m; i++)
    {
        HT[i].weight = 0;
        HT[i].lchild = 0;
        HT[i].parent = 0;
        HT[i].rchild = 0;
    }
    //创建非叶子结点即开始构建赫夫曼树
    for(i = n + 1; i <= m; i++)
    {
        Select(HT, i-1, &s1, &s2);
        //选出的两个权值最小的叶子结点组成一个新的二叉树
		//根为 i 结点
        HT[s1].parent = i;
        HT[s2].parent = i;
        HT[i].lchild = s1;
        HT[i].rchild = s2;
        //新结点权值的存入 
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
}

完成了赫夫曼树的构建,就要用它做点正事了,由此开始探讨 赫夫曼编码的生成

首先了解一下什么是赫弗曼编码——其实就是前缀码的生成,只不过这里要保证每一个生成的前缀码都不是其他编码的前缀,这就可以引入上述探讨得出的赫夫曼树了。

为什么赫夫曼树可以用来生成这样的前缀码呢?

我的理解:

  1. 因为赫夫曼树为每一个权值分配了一条路径,从根结点到某一权值结点的路径上不会出现其他其他权值结点,其实也就是最初的权值结点都成为了最终赫夫曼树的叶子结点。

  2. 赫夫曼树中所有叶结点的带权路径长度之和——WPL,它的值是比较小的,这也就是它被称作最优二叉树的原因。
    在这里插入图片描述

这一特性使其生成的编码不会彼此间的前缀,而且长度非常的短。

赫夫曼编码的生成

我们可以将 左路径设置为 0,右路径设置为 1, 依次将每条路径上的0,1编码存入字符数组里;
在这里插入图片描述
在上图中的赫夫曼树,从根结点出发,一次向叶结点探索,如果要得到A的前缀码,路径为 右——右,因此它的编码即为 11
再如 11 010 10 011 00 ,它的译码即为 ABCDE

这里应题目要求,提供了一个逆向生成的赫夫曼编码

代码段如下
//开始逆向生成赫夫曼编码
void CreatHuffmanCode(HuffmanTree &HT, HuffmanCode *HC, char *name, int n)
{
    int i;
    //编码的起始指针
    int start;
    //标记父结点 
    int p;
	//标记子结点 
    unsigned int c;
    //给n个编码的分配头指针
    HC=(HuffmanCode *)malloc((n+1) * sizeof(char *));
    //当前编码的空间
    char *cd = (char *)malloc(n * sizeof(char));
    //从右向左
    cd[n-1] = '\0';
    for(i = 1; i <= n; i++)
    {
        //初始化编码起始指针
        start = n - 1;
        //从叶子到根结点求编码
        for(c = i, p = HT[i].parent; p != 0; c = p, p = HT[p].parent)
        {
        	//从右到左的顺序编码入数组内
            if( HT[p].lchild == c)
                 cd[--start] = '0';  //左分支标0
            else
            	cd[--start] = '1';  //右分支标1
        }
        //为第i个编码分配空间并保存生成的编码 
        HC[i] = (char *)malloc((n - start) * sizeof(char));
        strcpy(HC[i], &cd[start]);
    }
    //释放cd开始下一组编码的生成 
    free(cd);
    //打印编码序列
    for(i = 1; i <= n; i++)
         printf("%c:%s\n", name[i], HC[i]);
}

不多喷了, 这道题题完整的AC代码是这样的

#include<stdio.h>
#include<string.h>
#include<stdlib.h> 
//haffman 树的结构
typedef struct
{
    //叶子结点权值
    unsigned int weight;
    //指向双亲,和孩子结点的指针
    unsigned int parent;
    unsigned int lchild;
    unsigned int rchild;
} Node, *HuffmanTree;
//这里选择了动态分配数组
typedef char *HuffmanCode;
//找出两个最小权值结点并用指针带回 
void Select(HuffmanTree HT, int n, int *s1, int *s2)
{
    int i = 0;
    int min;
    for(i = 1; i <= n; i++)
    {
        //找到第一个单结点 
        if(HT[i].parent == 0)
        {
            min = i;
            break;
        }
    }
    //继续遍历全部结点,找出权值最小的单节点
    for(i = 1; i <= n; i++)
    {
        //结点的父亲为空
        if(HT[i].parent == 0)
        {
            if(HT[i].weight < HT[min].weight)
            {
               min = i;
            }
        }
    }
    //找到了最小权值的结点,s1指向
    *s1 = min;
	//重复操作注意s1的干扰 
    for(i = 1; i <= n; i++)
    {

        if(HT[i].parent == 0 && i != (*s1))
        {
            min = i;
            break;
        }
    }
    for(i = 1; i <= n; i++)
    {
        if(HT[i].parent == 0 && i != (*s1))
        {
            if(HT[i].weight < HT[min].weight)
            {
               min = i;
            }
        }
    }
    *s2 = min;
}

//构建哈夫曼树
void CreateHuffmanTree(HuffmanTree &HT, int w[], int n)
{
	//赫夫曼结点数 
    int m = 2 * n - 1;
    //选取两个最小权值的结点
    int s1;
    int s2;
    int i;
    //为树结点分配内存 这里从1忽略了0号元素 
    HT = (HuffmanTree)malloc((m + 1) * sizeof(Node));
	//初始化树叶结点 
    for(i = 1; i <= n; i++)
    {
        HT[i].weight = w[i];
        HT[i].lchild = 0;
        HT[i].parent = 0;
        HT[i].rchild = 0;
    }
    //非叶子结点的初始化 
    for(i = n + 1; i <= m; i++)
    {
        HT[i].weight = 0;
        HT[i].lchild = 0;
        HT[i].parent = 0;
        HT[i].rchild = 0;
    }
    //创建非叶子结点即开始构建赫夫曼树
    for(i = n + 1; i <= m; i++)
    {
        Select(HT, i-1, &s1, &s2);
        //选出的两个权值最小的叶子结点组成一个新的二叉树
		//根为 i 结点
        HT[s1].parent = i;
        HT[s2].parent = i;
        HT[i].lchild = s1;
        HT[i].rchild = s2;
        //新结点权值的存入 
        HT[i].weight = HT[s1].weight + HT[s2].weight;
    }
}
//开始逆向生成赫夫曼编码
void CreatHuffmanCode(HuffmanTree &HT, HuffmanCode *HC, char *name, int n)
{
    int i;
    //编码的起始指针
    int start;
    //标记父结点 
    int p;
	//标记子结点 
    unsigned int c;
    //给n个编码的分配头指针
    HC=(HuffmanCode *)malloc((n+1) * sizeof(char *));
    //当前编码的空间
    char *cd = (char *)malloc(n * sizeof(char));
    //从右向左
    cd[n-1] = '\0';
    for(i = 1; i <= n; i++)
    {
        //初始化编码起始指针
        start = n - 1;
        //从叶子到根结点求编码
        for(c = i, p = HT[i].parent; p != 0; c = p, p = HT[p].parent)
        {
        	//从右到左的顺序编码入数组内
            if( HT[p].lchild == c)
                 cd[--start] = '0';  //左分支标0
            else
            	cd[--start] = '1';  //右分支标1
        }
        //为第i个编码分配空间并保存生成的编码 
        HC[i] = (char *)malloc((n - start) * sizeof(char));
        strcpy(HC[i], &cd[start]);
    }
    //释放cd开始下一组编码的生成 
    free(cd);
    //打印编码序列
    for(i = 1; i <= n; i++)
         printf("%c:%s\n", name[i], HC[i]);
}
int main(void)
{
    HuffmanTree HT;
    HuffmanCode HC;
    int *w, i, n, m;
    char *name;
    while(~scanf("%d", &n)) 
    {
	    w=(int *)malloc((n+1)*sizeof(int));
	    name=(char *)malloc((n+1)*sizeof(char));
	    for(i=1; i<=n; i++)
	    {
	    	getchar();
	        scanf("%c %d",&name[i], &w[i]);
	    }
	    CreateHuffmanTree(HT, w, n);
	    CreatHuffmanCode(HT, &HC, name, n);   	
	}
    return 0;
}

注释:

首先、
这篇博客里的些许内容借鉴了其他人的文章,毕竟数据结构的题目无论是设计,还是Debug,都太费劲了, 我选择了 最优路径。。。。。(流汗)

第二、
自从学习树的第一讲开始,就意识到以后的数据结构也会愈渐复杂,遇到的问题解决起来会越来越费劲,有些东西实现起来简直是肝到爆,一次次的调试更是弄得人心态爆炸,但不管怎么说——不能草草了事

最后、
老规矩,AC了就赶紧睡觉, 头发最重要。。。。。。。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值