关闭

数据结构和算法——Huffman树和Huffman编码

标签: Huffman树
1531人阅读 评论(0) 收藏 举报
分类:

Huffman树是一种特殊结构的二叉树,由Huffman树设计的二进制前缀编码,也称为Huffman编码在通信领域有着广泛的应用。在word2vec模型中,在构建层次Softmax的过程中,也使用到了Huffman树的知识。

在通信中,需要将传输的文字转换成二进制的字符串,假设传输的报文为:“AFTERDATAEARAREARTAREA”,现在需要对该报文进行编码。

一、Huffman树的基本概念

在二叉树中有一些基本的概念,对于如下所示的二叉树:

这里写图片描述

  • 路径

路径是指在一棵树中,从一个节点到另一个节点之间的分支构成的通路,如从节点8到节点1的路径如下图所示:

这里写图片描述

  • 路径长度

路径长度指的是路径上分支的数目,在上图中,路径长度为2。

  • 节点的权

节点的权指的是为树中的每一个节点赋予的一个非负的值,如上图中每一个节点中的值。

  • 节点的带权路径长度

节点的带权路径长度指的是从根节点到该节点之间的路径长度与该节点权的乘积:如对于1节点的带权路径长度为:2。

  • 树的带权路径长度

树的带权路径长度指的是所有叶子节点的带权路径长度之和。

有了如上的概念,对于Huffman树,其定义为:

给定n权值作为n个叶子节点,构造一棵二叉树,若这棵二叉树的带权路径长度达到最小,则称这样的二叉树为最优二叉树,也称为Huffman树。

由以上的定义可以知道,Huffman树是带权路径长度最小的二叉树,对于上面的二叉树,其构造完成的Huffman树为:

这里写图片描述

二、Huffman树的构建

由上述的Huffman树可知:节点的权越小,其离树的根节点越远。那么应该如何构建Huffman树呢?以上述报文为例,首先需要统计出每个字符出现的次数作为节点的权:

这里写图片描述

接下来构建Huffman树:

  • 重复以下的步骤:
    • 按照权值对每一个节点排序:D-F-T-E-R-A
    • 选择权值最小的两个节点,此处为D和F生成新的节点,节点的权重为这两个节点的权重之和,为2
  • 直到只剩最后的根节点

按照上述的步骤,该报文的Huffman树的生成过程为:

这里写图片描述

这里写图片描述

对于树中节点的结构为:

#define LEN 512
struct huffman_node{
        char c;
        int weight;
        char huffman_code[LEN];
        huffman_node * left;
        huffman_node * right;
};

对于Huffman树的构建过程为:

int huffman_tree_create(huffman_node *&root, map<char, int> &word){
        char line[MAX_LINE];
        vector<huffman_node *> huffman_tree_node;

        map<char, int>::iterator it_t;
        for (it_t = word.begin(); it_t != word.end(); it_t++){
                // 为每一个节点申请空间
                huffman_node *node = (huffman_node *)malloc(sizeof(huffman_node));
                node->c = it_t->first;
                node->weight = it_t->second;
                node->left = NULL;
                node->right = NULL;
                huffman_tree_node.push_back(node);
        }


        // 开始从叶节点开始构建Huffman树
        while (huffman_tree_node.size() > 0){
                // 按照weight升序排序
                sort(huffman_tree_node.begin(), huffman_tree_node.end(), sort_by_weight);
                // 取出前两个节点
                if (huffman_tree_node.size() == 1){// 只有一个根结点
                        root = huffman_tree_node[0];
                        huffman_tree_node.erase(huffman_tree_node.begin());
                }else{
                        // 取出前两个
                        huffman_node *node_1 = huffman_tree_node[0];
                        huffman_node *node_2 = huffman_tree_node[1];
                        // 删除
                        huffman_tree_node.erase(huffman_tree_node.begin());
                        huffman_tree_node.erase(huffman_tree_node.begin());
                        // 生成新的节点
                        huffman_node *node = (huffman_node *)malloc(sizeof(huffman_node));
                        node->weight = node_1->weight + node_2->weight;
                        (node_1->weight < node_2->weight)?(node->left=node_1,node->right=node_2):(node->left=node_2,node->right=node_1);
                        huffman_tree_node.push_back(node);
                }
        }

        return 0;
}

其中,map结构的word为每一个字符出现的频率,是从文件中解析出来的,解析的代码为:

int read_file(FILE *fn, map<char, int> &word){
        if (fn == NULL) return 1;
        char line[MAX_LINE];
        while (fgets(line, 1024, fn)){
                fprintf(stderr, "%s\n", line);
                //解析,统计词频
                char *p = line;
                while (*p != '\0' && *p != '\n'){
                        map<char, int>::iterator it = word.find(*p);
                        if (it == word.end()){// 不存在,插入
                                word.insert(make_pair(*p, 1));
                        }else{
                                it->second ++;
                        }
                        p ++;
                }
        }
        return 0;
}

当构建好Huffman树后,我们分别利用先序遍历和中序遍历去遍历Huffman树,先序遍历的代码为:

void print_huffman_pre(huffman_node *node){
        if (node != NULL){
                fprintf(stderr, "%c\t%d\n", node->c, node->weight);
                print_huffman_pre(node->left);
                print_huffman_pre(node->right);
        }
}

中序遍历的代码为:

void print_huffman_in(huffman_node *node){
        if (node != NULL){
                print_huffman_in(node->left);
                fprintf(stderr, "%c\t%d\n", node->c, node->weight);
                print_huffman_in(node->right);
        }
}

得到的结构与上图中的结构一致。

三、由Huffman树生成Huffman编码

有了上述的Huffman树的结构,现在我们需要利用Huffman树对每一个字符编码,该编码又称为Huffman编码,Huffman编码是一种前缀编码,即一个字符的编码不是另一个字符编码的前缀。在这里约定:

  • 将权值小的最为左节点,权值大的作为右节点
  • 左孩子编码为0,右孩子编码为1

因此,上述的编码形式如下图所示:

这里写图片描述

从上图中,E节点的编码为:00,同理,D节点的编码为1001

Huffman编码的实现过程为:

int get_huffman_code(huffman_node *&node){
        if (node == NULL) return 1;
        // 利用层次遍历,构造每一个节点
        huffman_node *p = node;
        queue<huffman_node *> q;
        q.push(p);
        while(q.size() > 0){
                p = q.front();
                q.pop();
                if (p->left != NULL){
                        q.push(p->left);
                        strcpy((p->left)->huffman_code, p->huffman_code);
                        char *ptr = (p->left)->huffman_code;
                        while (*ptr != '\0'){
                                ptr ++;
                        }
                        *ptr = '0';
                }
                if (p->right != NULL){
                        q.push(p->right);
                        strcpy((p->right)->huffman_code, p->huffman_code);
                        char *ptr = (p->right)->huffman_code;
                        while (*ptr != '\0'){
                                ptr ++;
                        }
                        *ptr = '1';
                }
        }


        return 0;
}

利用上述的代码,测试的主函数为:

int main(){
        // 读文件
        FILE *fn = fopen("huffman", "r");
        huffman_node *root = NULL;
        map<char, int> word;
        read_file(fn, word);
        huffman_tree_create(root, word);
        fclose(fn);
        fprintf(stderr, "pre-order:\n");
        print_huffman_pre(root);
        fprintf(stderr, "in-order:\n");
        print_huffman_in(root);

        get_huffman_code(root);
        fprintf(stderr, "the final result:\n");
        print_leaf(root);
        destory_huffman_tree(root);
        return 0;
}

print_leaf函数用于打印出每个叶节点的Huffman编码,其具体实现为:

void print_leaf(huffman_node *node){
        if (node != NULL){
                print_leaf(node->left);
                if (node->left == NULL && node->right == NULL) fprintf(stderr, "%c\t%s\n", node->c, node->huffman_code);
                print_leaf(node->right);
        }
}

destory_huffman_tree函数用于销毁Huffman树,其具体实现为:

void destory_huffman_tree(huffman_node *node){
        if (node != NULL){
                destory_huffman_tree(node->left);
                destory_huffman_tree(node->right);
                free(node);
                node = NULL;
        }
}

其最终的结果为:

这里写图片描述

参考文献

  • 《大话数据结构》
  • 《数据结构》(C语言版)
0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

【数据结构与算法】Huffman树&&Huffman编码(附完整源码)

赫夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有n个权值{w1,w2,...,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,...,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。 这里补充下树的带权路...
  • mmc_maodun
  • mmc_maodun
  • 2014-02-15 00:27
  • 23279

哈夫曼树

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

实现huffman树

 今晚实现了一个huffman树. 明晚实现huffman编码 /**//** 文件名:huffman.cpp* 描述:实现一个huffman树* 创建日期:2007-11-18*/#includeiostream>using nam...
  • apaolove
  • apaolove
  • 2007-11-19 00:30
  • 705

Huffman树

Huffman树在编码中有着广泛的应用。在这里,我们只关心Huffman树的构造过程。   给出一列数{pi}={p0, p1, …, pn-1},用这列数构造Huffman树的过程如下:   1. 找到{pi}中最小的两个数,设为pa和pb,将pa和pb从{pi}中删除掉,然后将它们的和加入到...
  • KASH_SHADOW
  • KASH_SHADOW
  • 2017-02-14 14:17
  • 159

Huffman树及其应用

最优二叉树(赫夫曼树)                 路    径:由一结点到另一结点间的分支所构成。         路径长度:...
  • qq_28602957
  • qq_28602957
  • 2016-06-01 10:56
  • 2105

哈夫曼树最优性的证明(思考良久)

哈夫曼树为什么就是最优树??怎么去证明!!?? 证明围绕着两个东西(一定要先看,而且要能够认同): 1. 现在还不知道树长什么模样,但不管怎样,可以确定:最小的两个权值是在整棵树的最下段的两片叶子,而且这两片叶子是兄弟关系 2.承认上面这句话,那么你就是认同最小的两个权值是是叶子而且是兄弟关系,...
  • qq_25847123
  • qq_25847123
  • 2015-10-09 09:22
  • 3323

Huffman树的实现

假设给定一个有n个权值的集合{w1,w2,w3,…,wn},其中wi>0(1<=i<=n)。若T是一棵有n个叶结点的二叉树,而且将权值w1,w2,w3…wn分别赋值给T的n个叶结点,则称T是权值为w1,w2,w3…wn的扩充二叉树。带有权值的叶节点叫着扩充二叉树的外结点,其余不带权...
  • Dakuan_chen
  • Dakuan_chen
  • 2017-06-06 16:58
  • 159

Huffman树

  • 2008-04-13 17:29
  • 133KB
  • 下载

huffman编码——原理与实现

哈夫曼算法原理 Wikipedia上面说的很清楚了,这里我就不再赘述,直接贴过来了。 1952年, David A. Huffman提出了一个不同的算法,这个算法可以为任何的可能性提供出一个理想的树。香农-范诺编码(Shanno-Fano)是从树的根节点到叶子节点所进行的的编码,...
  • abcjennifer
  • abcjennifer
  • 2012-09-26 14:51
  • 68937

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

看到的讲解huffman树的一篇比较好懂的博客,唯一不足的地方就是代码里面使用Malloc时没有强制类型转换。。。。 出处:http://blog.csdn.net/wtfmonking/article/details/17150499# 1、基本概念 a、路径和路径长度 若在一棵树中存在着...
  • Move_now
  • Move_now
  • 2016-11-29 20:02
  • 11772
    个人声明

    欢迎大家加群,探讨与机器学习相关技术相关的话题:


    101620539


    学习网站:www.wanwenonline.cn

    博客的主要内容主要是自己的学习笔记,并结合个人的理解,供各位在学习过程中参考,若有疑问,欢迎提出;若有侵权,请告知博主删除,原创文章转载还请注明出处。

    -----------------

    我写的书:

    Python机器学习算法

    购买链接:

    京东-Python机器学习算法
    个人资料
    • 访问:1371281次
    • 积分:11126
    • 等级:
    • 排名:第1669名
    • 原创:155篇
    • 转载:1篇
    • 译文:1篇
    • 评论:584条
    博客专栏
    联系我
    Email:zhaozhiyong1989@126.com

    最新评论