详解树、森林和二叉树

树的定义

树是n个结点的有限集,当n = 0时,称为空树

非空树应满足以下条件

有且仅有一个特定的结点,称为根,没有直接前驱,有零个或多个直接后继;

其余n-1个结点可以划分成m个互不相交的有限集,每个有限集又是一棵树,称为根的子树。每颗子树的根节点有且仅有一个直接前驱,但有零个或多个直接后继

树的基本术语

结点:包含一个数据元素及若干指向其子树的分支 

结点的度:结点拥有的子树个数

树的度:树内各节点的度的最大值

叶子:度为0的结点,也叫终端结点;

非终端结点与分支结点:度不为0的结点。除根结点外的分支结点称为内部结点

孩子:结点的子树的根称为该结点的孩子

双亲:该结点称为孩子的双亲

兄弟:同一双亲孩子之间称为兄弟

双亲在同一层的结点之间称为堂兄弟

路径,路径长度:路径指从根节点到任意结点所经过的结点的集合,且每一个都是后一个的后继,即均是不同层,最短下降的。路径长度为路径所通过的结点数目-1。

结点的层次:从根节点开始定义为第一层,以此类推。

深度:层次的最大层。

二叉树

定义

每个结点的度不大于2;

每个结点的孩子结点次序不能任意颠倒,因此树的孩子有左孩子和右孩子之分

满二叉树和完全二叉树

满二叉树↑ 

性质:

  • 二叉树的第i层上至多有2^(i-1)个结点;度为m的树第i层上至多有m^(i-1)个结点
  • 深度为k的二叉树至多有2^k-1个结点;深度为k的m叉树最多有m^k-1/m-1个结点
  • 对任意一棵二叉树,若终端结点数为n0,度为2的结点数位n2,则n0 = n2+1
  • 具有n个结点的完全二叉树的深度为K = (log2n)向下取整+1或(log2(n+1))向上取整
  • 对于完全二叉树中编号为i的结点:
    • i = 1,结点是二叉树的根
    • i > 1,双亲编号为(i/2)向下取整;
    • i为偶数时,是双亲结点的左孩子。双亲结点编号为i/2,,
    • i为奇数时,是双亲结点的右孩子。双亲结点的编号为(i-1)/2
    • 编号为i的结点的左孩子编号为2i,右孩子为2i+1
    • n为节点数,2i>n,i无左孩子;2i+1>n无右孩子
    • n为节点数
      • 为奇数则为每个结点都儿女双全
      • 为偶数则编号最大的分支结点只有左孩子

二叉树的存储结构(两种)

顺序存储和链式存储

顺序存储

按结点编号存在一个数组中,数组中下标位置为i-1

一般二叉树顺序存储:完全对照结点编号和下标顺序存储;

最坏情况下深度为k的右单支二叉树需2^k-1个存储空间

链式存储

二叉树遍历

递归实现

  • 先序遍历

    访问结点->先序遍历左孩子->先序遍历右孩子
  • 中序遍历

    中序遍历左孩子->访问结点->中序遍历右孩子
  • 后续遍历 

    后续遍历左孩子->后续遍历右孩子->访问结点

中序遍历的结果和先序、后序遍历的结果可以还原出一棵二叉树;但先序、后序遍历的结果并不能还原出二叉树

如何求输出的序列?

从根结点开始从左到右画线,每次通过结点记为一次通过;画完一圈每个结点会有三次通过,先序就是第一次通过时,输出结点内容;中序就是第二次通过时,输出结点内容;后序就是第三次通过时,输出结点内容。注意在终末结点应该踏步两次表示完成该结点的左右孩子遍历。

线索二叉树

为了增大空间利用率,当没有左孩子时,储存指向其前驱的指针;当没有右孩子时,储存指向其后继的指针,且多存两个tag,0时表示存储左孩子,1时表示存储的是前后继。

前后继的确定:先将二叉树按先中后的遍历方式输出,即可知道每个结点的前后继。若无前驱或后继,则指向NULL

访问二叉树找指定结点的前驱或后继

 

 

 

树的存储结构(三种方法)

双亲表示法

用一组连续的空间来存储书中的节点,在保存每个节点的同时附设一个指针来指示其双亲结点在表中的位置

DATA|Parent

 

以数组的形式存储;

优点:快速找到妈;

缺点:不好找孩子; 

孩子表示法

方案1、指针域的个数就等于树的度,树的度是树各个节点度的最大值

空链域个数S=nk-(n-1) = n(k-1)+1 //k度,n个节点

方案2、使每个结点的指针域的个数等于该结点的度。专门取一个位置来存放指针域的个数

人话:按存的孩子数分配存储空间

 由于各个节点的链表是不同的结构,加上要维护节点的度的数值,在运算上会带来时间上的损耗

方案3、孩子链表法

把每个节点的孩子结点排列起来,形成一个单链表,称为孩子链表。n个结点工业n个孩子链表(叶子结点的孩子链表为空表),而n个节点的数据和n个孩子链表的头指针又组成一个顺序表

 孩子兄弟表示法

存储结构与二叉树类似,便于实现树的各种操作 

 树转二叉树

  1. 在所有相邻兄弟结点间加一水平连线
  2. 对每个非叶节点k,除了其最左边的孩子结点外,删去k与其他孩子节点的连线。
  3. 所有水平线以左边节点为轴心顺时针旋转45度(……?),使之结构层次分明

森林转二叉树

森林也可以方便的用孩子兄弟链表。

  1. 将森林中每棵树转换成二叉树
  2.  第一棵二叉树不动,从第二棵开始,依次把后一棵二叉树的根节点作为前一个二叉树根结点的右孩子,当所有二叉树连在一起时,所得的二叉树就是森林转换得到的二叉树

二叉树还原为树或森林

将一棵二叉树还原为树或森林。

  1. 若某结点是其双亲的左孩子,则把该结点右孩子,右孩子的右孩子,右孩子的右孩子的右孩子...都与该结点的双亲结点用线连起来
  2. 删掉原二叉树中所有双亲结点与右孩子结点的连线
  3. 整理前两步所得的树或森林,好看一点

树与森林的遍历

树的遍历:先根遍历

若树非空,则遍历方法:

  1. 访问到根节点(读存储信息)
  2. 从左到右,依次先根遍历根节点的每一棵子树

等同于转换的二叉树进行先序遍历

树的遍历:后根遍历

若树非空,则遍历方法为:

  1. 从左到右,依次先后根遍历根节点的每一棵子树
  2. 访问根节点(读存储信息)

等同于转换的二叉树进行中序遍历

以上图为例,此时遍历的序列:EBHFGCDA

森林的遍历:先序遍历

若森林非空,则遍历方法为:

  1. 访问森林中第一棵树的根结点。
  2. 先序遍历第一棵树的根结点的子树森林
  3. 先序遍历除去第一棵树之后剩余的树构成的森林

等同于转换的二叉树进行先序遍历

森林的遍历:中序遍历 

 当森林非空,则遍历方法为

  1. 中序遍历森林中第一棵树的根节点的子树森林
  2. 访问第一棵树的根节点
  3. 中序遍历除去第一棵树之后剩余的树构成的森林

等同于转换的二叉树进行中序遍历

根据树的先根遍历和后根遍历序列还原树?

观察,成组的,先划分,先根遍历序列每一组的第一个和后根遍历序列每组的最后一个是相同的!且这个元素是该组的根结点。再继而进行分组,找对应。

根据森林的先、中遍历序列还原森林?

观察,同样的找分组,第一次找到的分组有几个就有几棵树,之后根据之前的原则还原即可

哈夫曼树及应用

路径长度:

        从一个节点到另一个节点需要走几步

带权路径长度:

        给每个路径加上权重

哈夫曼树

        设二叉树具有n个带权值的叶子节点,那么从根节点到各个叶子节点的路径长度与相应节点权值的乘积的和,叫做二叉树的带权路径长度

        具有最小带权路径长度的二叉树称为哈夫曼树

         只要叶子权重固定,就有固定的最优二叉树,带权路径长度最短,称为哈夫曼树。 

 给定权值w=(1,3,5,7)构造一棵哈夫曼树

        每次取权值最小的根节点,和为父节点,以此构造。

哈夫曼编码

        编码

        

        如何编码让电文的长度越短越好?

 

        设计长度不等的编码,满足任一个编码都不是另一个编码的前缀,则这样的编码称为前缀编码。 

 

 

猜你喜欢

转载自blog.csdn.net/Gelercat/article/details/127168869