简述最优二叉树(赫夫曼树)[通俗易懂]

简述最优二叉树(赫夫曼树)[通俗易懂]什么是哈夫曼树:给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(HuffmanTree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。哈夫曼树被用来进行哈夫曼编码,下面来介绍哈夫曼编码:假设需要传送的电文为“ABACCDA”,它只有四种字符,只需要用两个字符的串就可以分辨,假设A,B,C,D的编码分别是00,01,10,11,则该电文的编码便是:“00010010101100”,总长为14位,对方接收时,只需要二位一

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

什么是哈夫曼树:
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。

哈夫曼树被用来进行哈夫曼编码,下面来介绍哈夫曼编码:
假设需要传送的电文为“ABACCDA”,它只有四种字符,只需要用两个字符的串就可以分辨,假设A,B,C,D的编码分别是00,01,10,11,则该电文的编码便是:“00010010101100”,总长为14位,对方接收时,只需要二位一分就可以进行译码。在现实中尽可能希望编码总长尽可能短,如果采用对每个字符设计长度不同的编码,那么让电文中出现次数较多的字符尽可能采用短的编码,那么电文总长就可以减少。比如设计A,B,C,D的编码为0,00,1,01,则上述电文可转换为:“000011010”,总长为9。但是这样的电文无法翻译,例如“0000”就有多种译法。或是“AAAA”,或者是“ABA”,又或者是“BB”等,因此,若要设计长度不等的编码,则必须是任一个字符的编码都不是另一个字符的编码的前缀,这种编码称为前缀编码

那么如何得到电文长度最短的二进制前缀编码呢?设计电文总长度最短的二进制前缀编码即为以n种字符出现的频率做权值,设计一棵哈夫曼树的问题,由此得到的二进制前缀编码称为哈夫曼码

下面就来说说如何创建一棵赫夫曼树。

对于如何构造哈夫曼树,哈夫曼最早就给出了一个带有一般规律的算法,俗称哈夫曼算法:

(1)根据给定的n个权值{
w 1 w_1 w1, w 2 w_2 w2……, w n w_n wn}构成n棵二叉树的集合F = {
T 1 T_1 T1, T 2 T_2 T2……, T n T_n Tn},其中每棵二叉树中的 T i T_i Ti中只有一个带权为 w i w_i wi的根节点,其左右子树均为空。

(2)在F中选取两棵根节点的权值最小的树作为左右子树构成一棵新的二叉树,且置新的二叉树的根节点的权值为其左右子树上根节点的权值之和;

(3)在F中删除这两棵树,同时将新得到的二叉树加入F中;

(4)重复(2),和(3)步骤,直到F只含有一棵树为止。则这棵树就是哈夫曼树。

下面来看一个简单的案例:字母{a,b,c,d},出现的次数为{7,5,2,4},构建出哈夫曼树:

我们按照哈夫曼算法来构建哈夫曼树。
第一步选取其中最小的两个构建成二叉树:
在这里插入图片描述
第二步将6添加至权值序列中,删除之前的2和4:
权值列表变成{7,5,6}
重复第一二步,直到构建完成:
在这里插入图片描述
权值列表:{7,11}
在这里插入图片描述
至此,哈夫曼树构建完成:
在这里插入图片描述
带权路径长度为:WPL = 7×1 + 5×2 + 2×3 + 4×3 = 35
当然,哈夫曼树不是唯一的,比如上面的哈夫曼树也可以为:
在这里插入图片描述
带权路径长度为:WPL = 7×1 + 5×2 + 2×3 + 4×3 = 35

但是不管哈夫曼树怎样构造,最短带权路径长度只有一个。

由于哈夫曼树中没有度为1的结点(又叫做正则二叉树),则一棵有n个叶子结点的哈夫曼树共有2n-1个结点,可以存储在哎一个大小为2n-1的一维数组中。

下面来看一个复杂的例题:
已知某系统的通信联络中只可能出现8中字符,次数别为w= {5,29,7,8,14,23,3,11}
按照哈夫曼算法来构建哈夫曼树:
权值列表:{3,5,7,8,11,14,23,29}
在这里插入图片描述

权值列表:{7,8,8,11,14,23,29}
在这里插入图片描述
权值列表:{
8,11,14,15,23,29}
在这里插入图片描述
权值列表:{14,1519,23,29}
在这里插入图片描述
权值列表:{
19,23,29,29}
在这里插入图片描述
权值列表:{29,2942}
在这里插入图片描述
权值列表:{
4258}
在这里插入图片描述
至此,哈夫曼树已经构建完成。我们来算算其带权路径:
WPL = 23×2 + 11×3 + 5×4 + 3×4 + 29×2 + 14×3 + 7×4 + 8×4 = 271
我们规定向左为0,向右为1,则,该哈夫曼树为:
在这里插入图片描述
则这八种字符的编码分别为:{0110,10,1110,1111,110,00,011,010}

当然构造出的哈夫曼树不止一种,也可能为:
在这里插入图片描述该图的带权路径为:
WPL = 29×2 + 14×3 + 7×4 + 3×5 + 5×5 + 23×2 + 8×3 + 11×3 = 271
由此看出,哈夫曼树可能不同,但是带权路径一定是同一个最小值。

已经了解了思想了,现在该如何实现了。

class Node:
    def __init__(self, name, weight):
        self.name = name #节点名
        self.weight = weight #节点权重
        self.left = None #节点左孩子
        self.right = None #节点右孩子
        self.father = None # 节点父节点
    #判断是否是左孩子
    def is_left_child(self):
        return self.father.left == self

#创建最初的叶子节点
def create_prim_nodes(data_set, labels):
    if(len(data_set) != len(labels)):
        raise Exception('数据和标签不匹配!')
    nodes = []
    for i in range(len(labels)):
        nodes.append( Node(labels[i],data_set[i]) )
    return nodes


# 创建huffman树
def create_HF_tree(nodes):
    #此处注意,copy()属于浅拷贝,只拷贝最外层元素,内层嵌套元素则通过引用,而不是独立分配内存
    tree_nodes = nodes.copy()
    while len(tree_nodes) > 1: #只剩根节点时,退出循环
        tree_nodes.sort(key=lambda node: node.weight)#升序排列
        new_left = tree_nodes.pop(0)
        new_right = tree_nodes.pop(0)
        new_node = Node(None, (new_left.weight + new_right.weight))
        new_node.left = new_left
        new_node.right = new_right
        new_left.father = new_right.father = new_node
        tree_nodes.append(new_node)
    tree_nodes[0].father = None #根节点父亲为None
    return tree_nodes[0] #返回根节点

#获取huffman编码
def get_huffman_code(nodes):
    codes = { 
   }
    for node in nodes:
        code=''
        name = node.name
        while node.father != None:
            if node.is_left_child():
                code = '0' + code
            else:
                code = '1' + code
            node = node.father
        codes[name] = code
    return codes


if __name__ == '__main__':
    labels = ['A','B','C','D','E','F','G','H']
    data_set = [5,29,7,8,14,23,3,11]
    nodes = create_prim_nodes(data_set,labels)#创建初始叶子节点
    root = create_HF_tree(nodes)#创建huffman树
    codes = get_huffman_code(nodes)#获取huffman编码
    #打印huffman码
    for key in codes.keys():
        print(key,': ',codes[key])

Jetbrains全家桶1年46,售后保障稳定

运行结果:

A :  0001
B :  10
C :  1110
D :  1111
E :  110
F :  01
G :  0000
H :  001

代码参考自:哈夫曼树代码

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/207171.html原文链接:https://javaforall.net

(0)
上一篇 2025年8月1日 下午8:22
下一篇 2025年8月1日 下午9:01


相关推荐

  • python 生成随机矩阵_matlab建立m行n列矩阵

    python 生成随机矩阵_matlab建立m行n列矩阵导入模块random模块numpy中的random函数python中有两个模块可以生成随机数,该博客以的numpy模块为例进行生成随机数。(因为矩阵要生成大量的随机数据,故推荐使用numpy模块生成随机数)生成随机数(以矩阵为例)#生成随机矩阵importnumpyasnp# 设置随机种子,保证每次生成的随机数一样rd=np.random.RandomState(…

    2025年8月9日
    4
  • C++并发实战19:lock free编程

    C++并发实战19:lock free编程涉及到并行/并发计算时,通常都会想到加锁,加锁可以保护共享的数据,不过也会存在一些问题:1.由于临界区无法并发运行,进入临界区就需要等待,加锁使得效率的降低。多核CPU也不能发挥全部马力2.在复杂的情况下,很容易造成死锁,并发进程、线程之间无止境的互相等待。3.在中断/信号处理函数中不能加锁,给并发处理带来困难。4.加锁影响实时性,等待时间不确定5.优先级反转,优先级

    2022年7月19日
    22
  • OpenClaw:完全零成本在Windows本机部署OpenClaw免费大模型指南

    OpenClaw:完全零成本在Windows本机部署OpenClaw免费大模型指南

    2026年3月12日
    3
  • estimator使用

    estimator使用一 model fn 函数有 5 个输入参数 features labels mode params config 并输出一个 EstimatorSpe 实例 features input fn 的第一个输出 labels input fn 的第二个输出 mode 操作类型 是训练 预测还是评估 对应 tf estimator ModeKeys EVAL TRAIN PREDICT params 定义 Estimator 实例时传入的 params 属性 config 定义 Estimator 实例时

    2026年3月17日
    3
  • log4j使用教程_log4js

    log4j使用教程_log4js简介Log4J是Apache的一个开源项目(官网http://jakarta.apache.org/log4j),通过在项目中使用Log4J,我们可以控制日志信息输出到控制台、文件、GUI组件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以更灵活的控制日志的输出过程。方便项目的调试。组成Log4J主要由Loggers(日志记录器)、Ap…

    2025年9月13日
    7
  • 多少行代码可以申请软件著作权_python申请软件著作权

    多少行代码可以申请软件著作权_python申请软件著作权在申请软件专利或著作权时,如何快速统计源码行数。其实我们可以利用vs2010的文件搜索功能结合正则表达式,来遍历所有文件得出总行数.

    2026年2月13日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号