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

标签: Huffman树
3329人阅读 评论(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语言版)
查看评论

Huffman 编解码算法实现与压缩效率分析

一、Huffman编解码原理 1 熵,又称为“信息熵” (Entropy)      1.1 在信息论中,熵是信息的度量单位。信息论的创始人 Shannon 在其著作《通信的 数学理论》中提出了建立在...
  • amy2020
  • amy2020
  • 2017-05-14 14:35:42
  • 1198

霍夫曼编码(Huffman Coding)

霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种。 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编码表是通过一种评估来源符...
  • xgf415
  • xgf415
  • 2016-09-22 21:44:16
  • 32055

建huffman表

Z型扫描完之后就是huffman熵编码,实现起来就是查表过程。一共需要建立四张表(AC,DC,luminance,chrominance)。 1、先建立相应size code length统计数组 s...
  • rosenberg
  • rosenberg
  • 2007-09-30 20:22:00
  • 1556

Huffman树及其应用

最优二叉树(赫夫曼树)                 路    径:由一结点到另一结点间的分支所构成。         路径长度:路径上的分支数目。a→e的路径长度=2         树...
  • qq_28602957
  • qq_28602957
  • 2016-06-01 10:56:38
  • 2249

Huffman编码的C语言实现

Huffman 编码(1) Huffman Coding (霍夫曼编码)是一种无失真编码的编码方式,Huffman 编码是可变字长编码(VLC)的一种。 (2)Huffman 编码基于信源的概率统计...
  • yingqi_lok
  • yingqi_lok
  • 2017-04-19 22:12:30
  • 1489

huffman编码实现(详细实现)

1、概述      huffman编码是一种可变长编码(  VLC:variable length coding))方式,于1952年由huffman提出。依据字符在需要编码文件中出现的概率提供...
  • Qyee16
  • Qyee16
  • 2011-08-05 22:48:26
  • 32839

huffman算法实现文件的压缩与解压

本文采用哈夫曼编码的方式进行文件的压缩和解压缩,主要原理是通过huffman编码来表示字符,出现次数多的编码短,出现次数少的编码长,这样整体而言,所需的总的bit位是减少的。但是当大部分字符出现的频率...
  • qq_29503203
  • qq_29503203
  • 2016-11-05 11:04:28
  • 2753

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

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

huffman编码——原理与实现

哈夫曼算法原理 Wikipedia上面说的很清楚了,这里我就不再赘述,直接贴过来了。 1952年, David A. Huffman提出了一个不同的算法,这个算法可以为任何的可能性...
  • abcjennifer
  • abcjennifer
  • 2012-09-26 14:51:47
  • 71036

Huffman编码实现压缩、解压文件

Huffman编码:根据词频构建Huffman树,实现对文本的前缀编码。 1、统计文本中每个字符出现的次数,放入优先队列中,构建一棵空的二叉树; 2、取出频率最小的两个字符a、b,字符a、b的频率...
  • niuxiunan
  • niuxiunan
  • 2015-11-14 01:15:05
  • 2859
    个人资料
    专栏达人 持之以恒
    等级:
    访问量: 179万+
    积分: 1万+
    排名: 1473
    博客专栏
    最新评论