【数据结构】哈夫曼树、哈夫曼编码

简介: 【数据结构】哈夫曼树、哈夫曼编码

概念1:什么是路径?


在一棵树中,从一个结点到另一个结点所经过的所有结点,被我们称为两个结点之间的路径。


ea548384cb1546609e13c5ebc934e65d.png


概念2:什么是路径长度?


在一棵树中,从一个结点到另一个结点所经过的“边”的数量,被我们称为两个结点之间的路径长度。


c818e3e63e7641ac8afd460d0ca3c67c.png


概念3:什么是结点的权?


在实际应用中,人们往往会给树中的每一个结点赋予一个具有某种实际意义的数值,这个数值被称为该结点的权值。


c7cbe8aa49374870bb1a90a242fc3a15.png


概念4:什么是结点的带权路径长度?

树的每一个结点,都可以拥有自己的“权重”(Weight),权重在不同的算法当中可以起到不同的作用。


结点的带权路径长度,是指树的根结点到该结点的路径长度与该结点的权值的乘积。


69225112f00246e9af1be0d838b43140.png


概念5:什么是 树的带权路径长度?

在一棵树中,所有叶子结点的带权路径长度之和,被称为树的带权路径长度,也被简称为WPL。



d5f7eb4f11d84ca7ac78d9f8a0a70c0d.png

7bcba090101f4e7bad2c3cfbf24977ec.png


哈夫曼树是由麻省理工学院的哈夫曼博士于1952年发明,这到底是一颗什么样的树呢?


刚才我们学习了树的带权路径长度(WPL),而哈夫曼树(Huffman Tree)是在叶子结点和权重确定的情况下,带权路径长度最小的二叉树,也被称为最优二叉树。


🐋最优二叉树(哈夫曼树)


给定n个权值并作为n个叶结点,按一定规则构造一颗二叉树,使其带权路径长度达到最小值,则这棵二叉树被称为最优二叉树,也称为哈夫曼树。

416e5591198c4064b96114c7f4bfef1f.png

同一组数据的最优二叉树不唯一,因为没有限定左右子树,并且有权值重复时,可能树的高度都不唯一,唯一的只是带权路径长度之和最小。 构建哈夫曼树的时候即可以推导出。


🐋构建哈夫曼树


(1) 由已知给定的n个权值{w1,w2,w3,...,wn},构建一个有n棵二叉树所构建的森林

     F={T1,T2,T3,...Tn},其中每一棵二叉树中只含一个带权值为wi根节点,其左、右子树为空

(2) 在二叉树森林F中选取根节点的权值最小和次小的两棵二叉树,分别把它们作为左子树和右子树

    去构建一颗新二叉树,新二叉树的根节点权值为其左右子树根节点的权值之和。

(3) 作为新二叉树的左右子树的这两棵二叉树从森林F中删除,同时加入刚生成的新二叉树

(4) 重复步骤(2)和(3),直到森林F中只剩一颗二叉树为止,该二叉树就是哈夫曼树。


练习:  

有5个带权结点 {A,B,C,D,E},其权值分别为W={10,30,40,15,6},权值作为结点数据,绘制一颗哈夫曼 。


9e0a74fd71974a97b3e84fc6aa30b39e.png


🐋哈夫曼编码


编码诉求:对字符集进行二进制编码,使得信息的传输量最小。如果能对每一个字符用不同的长度的二进制编码,并且尽可能减少出现次数最多的字符的编码位数,则信息传送的总长度便可以达到最小。


哈夫曼编码:用电文中各个字符使用的频度作为叶结点的权,构造一颗具有最小带权路径长度的哈夫曼树,若对树中的每个==左分支赋予标记0,右分支赋予标记1==,则从根节点到每个叶结点的路径上的标记连接起来就构成一个二进制串,该二进制串被称为哈夫曼编码。


例题讲解:


         已知在一个信息通信联络中使用了8个字符:a、b、c、d、e、f、g和h,每个字符的使用频度分别为:6、30、8、9、15、24、4、12,试设计各个字符的哈夫曼编码。

786698339b494516a6a01029e1d8eafa.png

9fd40263404c43618206aaf63c3de58e.png

哈夫曼树进行译码


哈夫曼编码是一种前缀码,任何一个字符的编码都不是同一个字符集中另一个字符的编码的前缀。


译码过程是编码过程的逆过程。从哈夫曼树的根开始,从左到右把二进制编码的每一位进行判别,若遇到0,则选择左分支走向下一个结点;若遇到1,则选择右分支走向下一个结点,直至到达一个树叶结点,便求得响应字符。


练习:

有5个带权结点 {A,B,C,D,E},其权值分别为W={10,30,40,15,6},权值作为结点数据,绘制一颗哈夫曼,并编写哈夫曼编码。


263131b893af4d48b0d4f19e35ede6e1.png


A:1111

B:10

C:0

D:110

E:1110

编码:编码字符串 AABBEDCC-->111111111010111011000


🐋哈夫曼编码类【算法实现】


n个权值,组成哈夫曼树节点个数:2n-1


哈夫曼结点类 :

public class HuffmanNode {
    public int weight;// 权值
    public int flag; // 节点是否加入哈夫曼树的标志
    public HuffmanNode parent,lchild,rchild; // 父节点及左右孩子节点
    // 构造一个空节点
    public HuffmanNode(){
        this(0);
    }
    // 构造一个具有权值的节点
    public HuffmanNode(int weight){
        this.weight = weight;
        flag=0;
        parent=lchild=rchild=null;
    }
}

哈夫曼编码类 :

public class HuffmanTree {
    // 求哈夫曼编码的算法,w存放n个字符的权值(均>0)
    public int[][] huffmanCoding(int[] W){
        int n = W.length;  // 字符个数
        int m = 2*n -1;   //哈夫曼树的节点数
        // 构造n个具有权值的节点
        HuffmanNode[] HN = new HuffmanNode[m];
        int i = 0;
        for (; i<n ; i++) {
            HN[i] = new HuffmanNode(W[i]);
        }
        // 创建哈夫曼树
        for (i = n;  i<m ; i++) {
            // 在HN[0...1]选择不在哈夫曼树中,且权值weight最小的两个节点min1和min2
            HuffmanNode min1 = selectMin(HN,i-1);
            min1.flag = 1;
            HuffmanNode min2 = selectMin(HN,i-1);
            min2.flag = 1;
            // 构造 min1和min2的父节点,并修改父节点的权值
            HN[i] = new HuffmanNode();
            min1.parent=HN[i];
            min2.parent=HN[i];
            HN[i].lchild = min1;
            HN[i].rchild = min2;
            HN[i].weight = min1.weight+min2.weight;
        }
        //  从叶子到根 逆向求每个字符的哈夫曼编码
        int[][] HuffCode = new int[n][n]; // 分配n个字符编码存储空间
        for (int j =0;j<n;j++){
            // 编码的开始位置,初始化为数组的结尾
            int start = n-1;
            // 从叶子节点到根,逆向求编码
            for(HuffmanNode c = HN[j],p=c.parent;p!=null;c=p,p=p.parent){
                if(p.lchild.equals(c)){
                    HuffCode[j][start--]=0;
                }else{
                    HuffCode[j][start--]=1;
                }
            }
            // 编码的开始标志为-1,编码是-1之后的01序列
            HuffCode[j][start] = -1;
        }
        return HuffCode;
    }
    // 在HN[0...1]选择不在哈夫曼树中,且权值weight最小的两个节点min1和min2
    private HuffmanNode selectMin(HuffmanNode[] HN, int end) {
        // 求 不在哈夫曼树中, weight最小值的那个节点
        HuffmanNode min = HN[end];
        for (int i = 0; i < end; i++) {
            HuffmanNode h = HN[i];
            // 不在哈夫曼树中, weight最小值
            if(h.flag == 0 && h.weight<min.weight){
                min = h;
            }
        }
        return min;
    }
}

哈夫曼编码会用类似如下格式进行存储 :

48c67cfc45f94483bd0a5eaa29c6dba6.png

测试类 :

public class Demo02 {
    public static void main(String[] args) {
        // 一组权值
        int[] W = {6,30,8,9,15,24,4,12};
        // 创建哈夫曼树
        HuffmanTree tree = new HuffmanTree();
        // 求哈夫曼编码
        int[][] HN = tree.huffmanCoding(W);
        //打印编码
        System.out.println("哈夫曼编码是: ");
        for (int i = 0; i < HN.length; i++) {
            System.out.print(W[i]+" ");
            for (int j = 0; j < HN[i].length; j++) {
                if(HN[i][j] == -1){
                    for (int k = j+1; k <HN[i].length ; k++) {
                        System.out.print(HN[i][k]);
                    }
                    break;
                }
            }
            System.out.println();
        }
    }
}
相关文章
|
2月前
|
存储 C语言
数据结构基础详解(C语言): 树与二叉树的应用_哈夫曼树与哈夫曼曼编码_并查集_二叉排序树_平衡二叉树
本文详细介绍了树与二叉树的应用,涵盖哈夫曼树与哈夫曼编码、并查集以及二叉排序树等内容。首先讲解了哈夫曼树的构造方法及其在数据压缩中的应用;接着介绍了并查集的基本概念、存储结构及优化方法;随后探讨了二叉排序树的定义、查找、插入和删除操作;最后阐述了平衡二叉树的概念及其在保证树平衡状态下的插入和删除操作。通过本文,读者可以全面了解树与二叉树在实际问题中的应用技巧和优化策略。
|
5月前
|
机器学习/深度学习 存储
数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)
数据结构学习记录——哈夫曼树(什么是哈夫曼树、哈夫曼树的定义、哈夫曼树的构造、哈夫曼树的特点、哈夫曼编码)
136 1
|
6月前
|
存储 NoSQL 程序员
Redis -- 常用数据结构,认识数据类型和编码方式
Redis -- 常用数据结构,认识数据类型和编码方式
43 2
|
6月前
|
存储 NoSQL 算法
Redis源码、面试指南(2)内存编码数据结构(下)
Redis源码、面试指南(2)内存编码数据结构
60 4
|
6月前
|
存储 NoSQL API
Redis源码、面试指南(2)内存编码数据结构(上)
Redis源码、面试指南(2)内存编码数据结构
68 0
|
6月前
|
编译器
【数据结构】哈夫曼树编译码器【课程设计】
【数据结构】哈夫曼树编译码器【课程设计】
|
19天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
97 9
|
10天前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
19 1
|
12天前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。

热门文章

最新文章

下一篇
无影云桌面