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

简述最优二叉树(赫夫曼树)[通俗易懂]什么是哈夫曼树:给定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

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • cegui 0.8.7 安装和构建

    cegui 0.8.7 安装和构建cegui是一个开源GUI库,经过历史的验证和发展,变得非常庞大和复杂,但效率是有所保证的,常用于游戏开发。1.首先去CEGUI官网,点击进入下载界面。2.下载这两个,第一个是cegui

  • 关于在eclipse中中文汉字乱码的解决方式[通俗易懂]

    关于在eclipse中中文汉字乱码的解决方式[通俗易懂]很多童鞋反应在吧项目导入到eclipse(myeclipse)时中文会有乱码,修改了编码格式后还是乱码,这里给大家介绍一下关于中文乱码时修改编码的注意事项: 当在eclipse中打开一个文件后发现有中文乱码后,千万不能修改这个文件内容,一旦改过这个文件的内容,那怎么修改编码也没用了,只能重新导入。 当打开文件发现乱码后第一步是关闭这个文件,然后在这个文件上右键,选择属性,然后选择编…

  • string转map_map转bean对象

    string转map_map转bean对象前提:String为Json类型字符串maven<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.0</version></dependency>转换

  • Ubuntu 优化、美化(主题、终端)[通俗易懂]

    Ubuntu 优化、美化(主题、终端)[通俗易懂]Ubuntu优化、美化(主题、终端)零效果图一优化Ubuntu\1系统更新\2安装GDebi(第三方软件安装)\3安装搜狗输入法\4软件卸载,安装4.1卸载libreOffice安装WPS4.2卸载掉亚马逊链接4.3卸载firebox浏览器安装Chrome/Chromium浏览器\5修改更新源\6vim配置\6菜单栏位置\7二美化Ubuntu\1主题1.1安装unity-tweak-tool:1.2Flatabulous主题\

  • 如何使用手机免费将PDF转Word还不限页数

    如何使用手机免费将PDF转Word还不限页数手机如何将PDF转换成Word?有时一些PDF资料需要修改才能使用,电脑端的修改已经很复杂了,更何况手机端安装软件和使用都更困难,而且有一些PDF文档本身就是扫描版无法进行修改,那么我们就只能将PDF转成Word后再编辑。目前一些能搜索的免费转换工具大多都付费,就算免费使用的也是很多限制,比如只能转换前3页或前5页,那么如何免费将手机里的PDF转换成Word还不限制页数呢?下面分享一个简单好用的方法,只需要三步就能轻松完成转换。第一步,首先打开手机的浏览器,输入speedpdf进行搜索,一般搜索结果第一

  • 借助栈来实现单链表的逆置运算_中缀后缀表达式转换

    借助栈来实现单链表的逆置运算_中缀后缀表达式转换原题链接算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。请设计程序将中缀表达式转换为后缀表达式。输入格式:输入在一行中给出不含空格的中缀表达式,可包含+、-、*、\以及左右括号(),表达式不超过20个字符。输出格式:在一行中输出转换后的后缀表达式,要求不同对象(运算数、运算符号)之间以空格分隔,但结尾不得有多余空格。输入样例:2+3*(7-4)+8/4输出样例:2 3 7 4 – * + 8 4 / +注意

发表回复

您的电子邮箱地址不会被公开。

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