zoukankan      html  css  js  c++  java
  • 树的数据结构

    树、森林与二叉树的转换

    1、树转换为二叉树

    由于二叉树是有序的,为了避免混淆,对于无序树,我们约定树中的每个结点的孩子结点按从左到右的顺序进行编号。

    将树转换成二叉树的步骤是:

    (1)加线。就是在所有兄弟结点之间加一条连线;

    (2)抹线。就是对树中的每个结点,只保留他与第一个孩子结点之间的连线,删除它与其它孩子结点之间的连线;

    (3)旋转。就是以树的根结点为轴心,将整棵树顺时针旋转一定角度,使之结构层次分明。

    2、森林转换为二叉树

    森林是由若干棵树组成,可以将森林中的每棵树的根结点看作是兄弟,由于每棵树都可以转换为二叉树,所以森林也可以转换为二叉树。

    将森林转换为二叉树的步骤是:

    (1)先把每棵树转换为二叉树;

    (2)第一棵二叉树不动,从第二棵二叉树开始,依次把后一棵二叉树的根结点作为前一棵二叉树的根结点的右孩子结点,用线连接起来。当所有的二叉树连接起来后得到的二叉树就是由森林转换得到的二叉树。

    3、二叉树转换为树

    二叉树转换为树是树转换为二叉树的逆过程,其步骤是:

    (1)若某结点的左孩子结点存在,将左孩子结点的右孩子结点、右孩子结点的右孩子结点……都作为该结点的孩子结点,将该结点与这些右孩子结点用线连接起来

    (2)删除原二叉树中所有结点与其右孩子结点的连线;

    (3)整理(1)和(2)两步得到的树,使之结构层次分明。

     

    什么是哈夫曼树?(最优二叉树)

    一. 目的:找出存放一串字符所需的最少的二进制编码

    二.  原理:首先统计出每种字符出现的频率!(也可以是概率)//权值

    让我们先举一个例子。

    判定树:

            在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设计直接影响着程序的执行效率。例如,编制一个程序,将百分制成绩转换成五个等级输出。

    若考虑上述程序所耗费的时间,就会发现该程序的缺陷。在实际中,学生成绩在五个等级上的分布是不均匀的。当学生百分制成绩的录入量很大时,上述判定过程需要反复调用,此时程序的执行效率将成为一个严重问题。

    但在实际应用中,往往各个分数段的分布并不是均匀的。下面就是在一次考试中某门课程的各分数段的分布情况: 

    第一种构造方式:

    第二种构造方式:

     

    这两种方式,显然后者的判定过程的效率要比前者高。在也没有别地判定过程比第二种方式的效率更高。
     
    我们称判定过程最优的二叉树为哈夫曼树,又称最优二叉树
     

    定义哈夫曼树之前先说明几个与哈夫曼树有关的概念:

    路径:           树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。

    路径长度:         路径上的分枝数目称作路径长度。   

    树的路径长度: 从树根到每一个结点的路径长度之和。

    结点的带权路径长度:在一棵树中,如果其结点上附带有一个权值,通常把该结点的路径长度与该结点上的权值之积 称为该结点的带权路径长度(weighted path length)

    什么是权值? ?( From 百度百科 )

    计算机领域中(数据结构)

      权值就是定义的路径上面的值。可以这样理解为节点间的距离。通常指字符对应的二进制编码出现的概率。

      至于霍夫曼树中的权值可以理解为:权值大表明出现概率大!

      一个结点的权值实际上就是这个结点子树在整个树中所占的比例.

      abcd四个叶子结点的权值为7,5,2,4. 这个7,5,2,4是根据实际情况得到的,比如说从一段文本中统计出abcd四个字母出现的次数分别为7,5,2,4. 说a结点的权值为7,意思是说a结点在系统中占有7这个份量.实际上也可以化为百分比来表示,但反而麻烦,实际上是一样的.

      例如:频率表 A:60,     B: 45,    C: 13    D: 69    E: 14    F: 5   G: 3

    第一步:找出字符中最小的两个,小的在左边,大的在右边,组成二叉树。

          在频率表中删除此次找到的两个数,并加入此次最小两个数的频率和

    FG最小,因此如图,从字符串频率计数中删除FG,并返回FG的和 8频率表

     

     重复第一步:

    频率表 A:60,    B:45,   C:13   D:69   E:14   FG:8

    最小的是 FG:8 与 C:13,因此如图,并返回FG 与 C的和:21给频率表。

     

    重复第一步:

    频率表 A:60    B: 45   D: 69   E: 14   FGC: 21

    如图

     

    重复第一步

    频率表 A:60    B: 45   D: 69  FGCE: 35

     

    重复第一步

    频率表 A:60   D: 69  FGCEB: 80

     

    重复第一步

    频率表 AD:129  FGCEB: 80

     

    添加 0 和 1,规则左0 右1

     

    频率表 A:60,    B:45,   C:13   D:69   E:14   F:5  G:3

    每个字符的二进制编码为(从根节点 数到对应的叶子节点,路径上的值拼接起来就是叶子节点字母的应该的编码)

    A:10

    B:01

    C:0011

    D:11

    E:000

    F:00101

    G:00100

    那么当我想传送 ABC时,编码为 10 01 0011

    补充一下白话版:

    大家观察 出现得越多的字母,他的编码越短 ,出现频率越少的字母,他的编码越长。

    在信息传输过程中,如果这个字母越多,那么我们希望他越瘦小(编码短)这样占用的编码越少,其实编码长的字母也是让频率比它多的字母把编码短的位子都占用后,他才去占用当前最短的编码。至此让总的编码长度最短。

    且要保证长编码的不与短编码的字母冲突:

    比如 不能出现 读码 读到 01  还有长编码的 字母为011,如果短编码为一个长编码的左起子串,这就是冲突,意思就是说读到当前位置已经能确定是什么字母时不能因为再读取一位或几位让这个编码能表示另外的字母,

    但哈夫曼树(最优二叉树)在构造的时候就避免了这个问题。为什么能避免呢,因为哈夫曼树的它的字母都在叶子节点上,因此不会出现一个字母的编码为另一个字母编码左起子串的情况。

    ----原文补充----

    我再补充一下:

    为什么要保证长编码不与短编码冲突?

    冲突情况:如果我们已经确定D,E,F,G 用 01 ,010 ,10,001的2进制编码来传输了。那么想传送FED时,我需要传送1001001,接收方可以把它解析为FDG,当然也能解析为FED,他两编码一样的,这就是编码冲突,显然这是不行的,就像我说压脉带,你如果是日本人会理解为 (你懂得),这就是发出同一种语,得出不同的意的情况。所以不能让一个字母的二进制代表数,为另一个字母的二进制代表数的子串。但为什么实际要求只是左起子串呢而不是绝对意义上的子串呢,因为计算机从数字串的左边开始读,如果从右边读,那么可以是右起(无奈)。你又可以问了为什么是左起或右起不直接规定不能是子串呢,比如说原文中B就是C,F,G的子串,那这不就不符合规则了么。这里是为了哈夫曼的根本目的,优化编码位占用问题,如果完全不能有任何子串那么编码将会非常庞大。但这里计算机是一位一位的·读取编码的,只需要保证计算机在读取中不会误判就行。并且编码占用最少。

     
     
  • 相关阅读:
    <转>iOS SDK中使用NSXMLParser解析XML
    [原创]闲来无事,写了个c#的数据库附加工具,现附上源代码
    [转载]php数组操作foreach、each、reset、list
    [原创]xml序列化
    [原创]一个简单的药店用的会员积分管理系统
    [原创]我的cms项目
    简洁、标准的对联广告代码
    C#混淆 xenocode使用说明
    .net 随机数
    [原创]c# as用法
  • 原文地址:https://www.cnblogs.com/xiaozhongfeixiang/p/11592008.html
Copyright © 2011-2022 走看看