树与二叉树(1)

定义与术语

树的定义

   是 n(n>=0) 个节点的有限集 T,当 n = 0 时,称为 空树,当 n>0 时,有且仅有一个 根节点,当 n>1时,除根节点外的其它节点可以分为 m(m>0) 个互不相交的有限集 T1,T2....... Tm,其中的每个 Ti 本身也是一棵树,称为 T子树

相关术语

  • 节点的度
    节点拥有的子树的个数称为该节点的 度(degree)

  • 树的度
    树中节点的度的最大值称为树的 度(degree)

  • n 度 树
    为 n 的树

  • 叶子节点(终端节点)
    为0的节点称为 叶子节点

  • 分支节点(非叶子节点)
    不为 0 的节点称为 分支节点

  • 双亲(parent) 与 孩子(child)
    若节点 C 为 节点 P 子树的根,则 P 是 C 的双亲,C 是 P 的孩子

  • 节点的层
    规定树的 的层为1,其余任何节点的层为其 双亲 的层加1

  • 树的深度(deepth)
    树中节点层的最大值称为树的 深度

  • 兄弟(sibling)
    同一双亲的节点互为 兄弟节点

  • 堂兄弟
    同一层的节点互为 堂兄弟节点

  • 祖先
    到某一节点所经过的所有分支上的节点称为该节点的 祖先

  • 子孙
    一个节点的所有子树节点称为该节点的 子孙

  • 有序树
    如果任何一个节点的各棵子树,规定从左到右是有次序的,即不能调换位置,则称该树是 有序树

  • 无序树
    如果任何一个节点的各棵子树,规定从左到右是无次序的,即可以调换位置,则称该树是 无序树

  • 森林
    m(m>=0) 棵不相交的树的集合称为 森林

@森林|center

  任何一棵非空树都可以表示为 二元组 Tree = (root,F),其中 root 表示该树的 F 称为 子树森林


### 树的相关基本操作

  与树相关的操作可以分为三类:查找插入删除

查找类

操作名称 功能描述
Root(T) 求树的根节点
Value(T,cur_e) 求当前节点的值
Parent(T,cur_e) 求当前节点的双亲节点
LeftChild(T,cur_e) 求当前节点的最左孩子
RightSibling(T,cur_e) 求当前节点的右兄弟
TreeEmpty(T) 判断当前树是否为空
TreeDepth(T) 获取当前树的深度
TraverseTree(T,Visit()) 遍历树

#### 插入
操作名称 功能描述
Init(&T) 初始化空树
Create(&T,definition) 按照定义构造树
Assign(&T,cur_e,val) 给当前节点赋值
InsertChild(&T,&P,i,c) 将以c 为根的树插入为节点 p 的第 i 棵子树

删除

操作名称 功能描述
Clear(&T) 清空树
Destory(&T) 销毁树的结构
DeleteChild(&T,&p,i) 删除树 T 的p 节点的第 i 棵子树

二叉树

二叉树的定义及特点

  二叉树的定义采用递归的方式进行定义,如下:

  二叉树 是 n(n>=0) 个节点的有限集合,可以为空树,或者由一个根节点和两棵互不相交的、分别称为左子树和右子树的二叉树组成。
  

  与普通的树相比,二叉树具有以下的特点:

  • 每个节点最多具有2棵子树(即不存在度大于2的节点)
  • 二叉树的左右子树具有次序,不能任意的颠倒

#### 二叉树的性质 1. 性质1:在二叉树的第 i(i>=1) 层上至多有 2i-1 个节点。可由归纳法证明
2. 性质 2 :深度为 k 的二叉树最多有 2k - 1 个节点,可由性质 1 得,

20 + 21 + ...... + 2k - 1 = 2k - 1

  1. 性质 3 : 在二叉树中,叶子节点数 n0 与 度为2的节点数 n2 满足如下的关系:n0 = n2 + 1 。

#### 特殊的二叉树
满二叉树

深度为 k 且有 2k -1 个节点的二叉树,称为 满二叉树(full binary tree)

满二叉树具有如下的特点:

  1. 每一层上的节点数都达到最大,叶子节点都分布在第 k 层
  2. 不存在度为 1 的节点
  3. n 个节点的二叉树的深度为 log2(n + 1)

##### 完全二叉树 深度为 k 并且具有 n 个节点,当且仅当每个节点都与同深度的满二叉树中编号从 1 到 n的节点一一对应,这样的二叉树称为 **完全二叉树** 或 **顺序二叉树** 。满二叉树一定为完全二叉树,反之不成立。
![@完全二叉树|center](http://omy4jz9m8.bkt.clouddn.com/.1513060842329.png)
![@非完全二叉树|center](http://omy4jz9m8.bkt.clouddn.com/.1513060895037.png)

  在上图的 T2 中,在满二叉树中本应编号为 6 的位置,这里却编号为 4,因此其不是完全二叉树。

  深度为 k 的完全二叉树,具有如下的性质:
  1) 任意节点 i,记其左右子树的深度的深度分别为 LhiRhi,则 Lhi - Rhi 等于 0 或者 -1,换句话说,就是叶子节点只可能出现在层次最大的。
  2) 完全二叉树的节点数 n 满足 2k-1 - 1<n<=2k - 1

  3) 如果一个完全二叉树具有 n 个节点,则其至少有 Log2n + 1 个节点,如果 Log2不为整数,则向下取整。也可以说是至少有 Log2(n + 1) 个节点,但结果如果不为整数要向上取整。
  

  4) 若对于具有 n 个节点的完全二叉树,从上至下,从左至右进行 1 到 n 的编号,则对于完全二叉树中任意编号为 i 节点,具有如下的特点:

i. 若 i = 1,则该节点是二叉树的根,无双亲,否则,i/2(向下取整)

ii. 若 2i>n,则该节点无左孩子,否则,编号 2i 的节点为其左孩子节点

iii. 若 2i + 1>n,则该节点没有右孩子,否则,编号 2i 的节点为其右孩子节点

#### 二叉树的存储结构   二叉树可以有两种的存储方式:`顺序存储` 和 `链式存储`。
顺序存储

  顺序存储 即使用数组对二叉树中的节点进行存储,如下面的完全二叉树便可以使用一维数组进行存储:
  

  @完全二叉树|left

  @完全二叉树顺序存储|right




  对于一般的二叉树,仍然可以按照上述的方式进行存储,无节点处使用 0 填充,表示 虚节点

顺序存储的特点
  1. 使用一组地址连续的存储单元,以层次顺序存储二叉树的数据元素,节点的相对位置包含着节点之间的关系
  2. 存在大量的空置的存储空间,存储空间利用率低
  3. 插入、删除操作不方便

##### 链式存储   **链式存储** 即在每个节点中除了存储节点的数据值外,还存储两个指针,分别指向当前节点的左孩子与右孩子,使用此种存储方式的链表称为 **二叉链表**。如果有需要,还可以增加一个指向当前节点双亲的指针,这样的链表称为 **三叉链表**。      ![@二叉树T1|center](http://omy4jz9m8.bkt.clouddn.com/.1513073013049.png)

  @T1的链式存储|center

  使用链式存储的方式,便可以很好的克服顺序存储的缺点。

二叉链表性质:在具有 n 个节点的二叉链表中,存在有 n + 1 个空链域

二叉树的遍历

  二叉树的遍历是指从根节点出发,按照某种次序依次访问二叉树中的所有节点,使得每个节点被访问依次且仅访问1次。因此,所有的二叉树遍历算法的时间复杂度都为 O(n),其中 n 为二叉树中节点的个数。
  经过遍历,可以使得二叉树中的非线性排列,按照访问的顺序变为某种线性排列,遍历是二叉树(树)进行查找、插入、删除等其它操作的基础。
  
现记,
- D 表示访问根节点,输出根节点
- L 表示递归遍历左子树
- R 表示递归遍历右子树

  则有一下几种遍历方式(顺序)
自左向右访问有

  1. DLR,先序遍历(PreOrder),先根
  2. LDR,中序遍历(InOrder),中根
  3. LRD,后序遍历(PostOrder),后根

自右向左有 1. **DRL**,逆先序遍历 2. **RDL**,逆中序遍历 3. **RLD**,逆后序遍历    ##### 先序遍历   先序遍历的递归定义如下,若二叉树为空,则遍历结束,否则执行以下的步骤:   1. 访问根节点   2. 先序遍历根的左子树   3. 先序遍历根的右子树

  按照上述的定义,如下的二叉树 T ,按照先序遍历,则产生如下的序列:
  @二叉树T|center

先序遍历序列:A - B - E - F - C - D - G

基于此,二叉树的先序遍历(基于二叉链表)现如下(C#):

        public void PreOrderTraversal(Node node)
        {
            if (node == null || node.Data == null)
                return;

            Console.WriteLine(node.Data);
            PreOrderTraversal(node.LeftChild);
            PreOrderTraversal(node.RightChild);
        }

  单独给出一个先序遍历的序列,并不能唯一确定一棵二叉树,至少存在两棵二叉树对应着同一先序序列,例如,上面的二叉树T,G节点为 D 节点的左孩子和右孩子时,先序遍历的结果是一样的。但如果在序列中包含虚节点,那么此时便可以通过先序序列唯一确定一个二叉树。


##### 中序遍历

  中序遍历的递归定义如下,如果树为空则遍历结束,否则执行如下步骤:

  1. 中序遍历根的左子树
  2. 访问根节点
  3. 中序遍历根的右子树

按照上述定义及步骤,上面的二叉树T 的中序遍历序列如下所示:

二叉树T 的中序遍历序列: E - B - F - A - C - G - D

  基于此,二叉树的中序遍历(基于二叉链表)现如下(C#):

        public void InOrderTraversal(Node node)
        {
            if(node == null || node.Data == null)
            {
                return;
            }

            InOrderTraversal(node.LeftChild);
            Console.WriteLine(node.Data);
            InOrderTraversal(node.RightChild);
        }

##### 后序遍历

  先序遍历的递归定义如下,若二叉树为空,则遍历结束,否则执行以下的步骤:

  1. 后序遍历根节点的左子树
  2. 后序遍历根节点的右子树
  3. 访问根节点

 按照上述定义及步骤,上面的二叉树T 的中序遍历序列如下所示:

二叉树T 的后序遍历序列: E - F - B - G - D - C - A

   基于此,二叉树的中序遍历(基于二叉链表)现如下(C#):

		public void PostOrderTraversal(Node node)
        {
            if(node == null || node.Data == null)
            {
                return;
            }

            PostOrderTraversal(node.LeftChild);
            PostOrderTraversal(node.RightChild);
            Console.WriteLine(node.Data);
        }

##### 根据遍历序列确定二叉树   在给定中序遍历序列和后序遍历序列的情况下,可以唯一确定一棵二叉树,如下:   中序序列:`B - D - C - E - A - F - H - G`   后序序列:`D - E - C - B - H - G - F - A`

  根据此,便可确定唯一的二叉树,如下
|center

  分析过程如下,

  1. 由后序遍历的特征可以得知,后序序列的最后一个元素为二叉树的根节点,即 A 为二叉树的根节点。

  2. 根据中序遍历的特征得知,根节点必在中序序列的中间,因此,可以得出 BDCE 属于二叉树的左子树,FHG 属于二叉树的右子树。

  3. 根据后序序列及后序遍历的特征,可以得出 B为 根节点 A 的左孩子,F为根节点 A 的右孩子,因为,B 是左子树后序遍历的最后一个节点,F 为右子树后序遍历的最后一个节点。

  4. 根据 B 为左子树的根节点及中序序列 B - D - C - E ,可以知道 B 无左子树,因为如果有,B 不可能为左子树中序遍历的第一个节点。

  5. 根据中序序列和后序序列左子树除 B 外的其它三个节点的序列,便可确定 C 为 B 的右孩子,D 和 E 为 C 的子节点。

  6. 根据中序序列的 F - H - G说明 F 节点无左子树,理由同步骤 4。

  7. 根据后序序列中的 H - G 说明 G 为 F 的 右子树,再根据中序的 H - G便可确定 H 为 G 的左孩子。

  除此之外,在其它的遍历序列组合或者某一个遍历序列,都不能唯一确定一棵二叉树。
  

线索二叉树

线索二叉树的意义及定义

  前面说过,通过遍历可以将非线性结构的二叉树转化为线性的序列,在线性的序列中,存在节点的前驱元素和后继元素,但在二叉链表中只存在节点的左孩子、右孩子节点,如果要查看节点的前驱和后继元素,只能动态产生这些信息。
  |center

  在具有 n 个节点的二叉链表中,具有 n*2 个链域,除根之外,每个节点有一个指向其的指针,共具有 n - 1 个指针,因此,具有 2n - (n - 1) = n + 1 个空的链域,线索二叉树 便是使用这些空的链域存储当前节点相关的前驱和后继信息的二叉树。指向前驱和后继的指针便称为 线索


##### 线索二叉树的节点结构

  为了区分节点中的一个指针是线索指针还是指向孩子节点的指针,需要在节点结构中添加两个标志位,添加后的节点的结构如下示:

@线索二叉树节点结构|center

  如果当前节点有左孩子节点,则 lchild 指针指向其左孩子节点,Ltag 标志位设置为 0,否则,Ltag 标志位设置为1,表示 lchild 指针指向其前驱节点。
  如果当前节点有右孩子节点,则 rchild 指针指向其左孩子节点,Rtag 标志位设置为0,否则,Rtag 标志位设置为1,表示 rchild 指针指向其后继节点。

线索二叉树的整体结构

  在线索二叉树中一般会设有一个头节点,令其 lchild 指针指向二叉树的根节点,同时将其 Ltag 设置为0,Rtag 设置为 1。
  并将该节点作为遍历的第一个节点的前驱元素和遍历的最后一个节点的后继元素,最后使用头指针指示该头节点。
  下图表示一个空二叉树的线索链表的结构,其 lchildrchild指针均指向头节点本身,Ltag 为0,Rtag 为1
  @空二叉树的线索链表|center

相关概念术语
  • 由以上结构构成的二叉链表称为二叉树的线索链表
  • 节点中指示其前驱和后继的链域称为线索
  • 加上线索的二叉树称为线索二叉树
  • 通过某种规则遍历将二叉树转化为线索二叉树的过程称为二叉树的 线索化
    • 通过先序遍历得到的线索二叉树称为 先序线索二叉树
    • 通过中序遍历得到的线索二叉树称为 中序线索二叉树
    • 通过后序遍历得到的线索二叉树称为 后序线索二叉树

  现有二叉树 T
|二叉树T

  具有前驱和后继的前序遍历线索二叉树
@具有前驱和后继的前序遍历线索二叉树

  具有前驱和后继的中序线索二叉树
@具有前驱和后继的中序线索二叉树

  具有前驱和后继的后序线索二叉树
@具有前驱和后继的后序线索二叉树


本文相关代码

posted @ 2017-12-21 11:10  空城守望城空  阅读(322)  评论(0编辑  收藏  举报