数据结构之树和二叉树(六)

定义

线性表讲解的是一对一的线性结构,可现实中,还有许多一对多的情况需要处理,因此,我们需要研究一对多的数据结构——树,运用其各种特性,来解决编程中遇到的相关问题。
树(Tree)是n(n≥0)个结点的有限集,n=0时称为空树。在任意一颗非空树中,具体定义如下:
(1).有且仅有一个特定的结点,称之为根(Root)的结点
(2).当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、……、Tm,其中每一个集合本身又是一棵树,并且称之为根的子树(SubTree)
如图所示:
树和子树
树的定义其实就是我们讲数据结构和算法之成数据结构概论(一)中提到的递归的方法,即树的定义之中还有用到了树的概念,这是一种比较新的定义方法。下图中的子树T1、子树T2和子树T3就是根结点A的子树,H、I、K组成的树又是以D为结点的子树。
这里写图片描述
【注意】
(1).当n>0时,根结点是唯一的,不可能存在多个根结点
(2).当m>0时,子树的个数没有限制,但它们一定是互不相交的。如下图所示,便不符合树的定义,因为它有相交的子树
非树情形


结点的分类

树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树称之为结点的度。度为0的结点,称为叶结点或终端结点;度不为0的结点,称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度数数内歌结点度的最大值。如下图所示,由于这棵树结点C的度为3且为最大值,所以树的度也为3。
树的度
结点的子树的根称之为结点的孩子,相应的,该结点称之为孩子的双亲
同一个双亲的孩子之间互称兄弟。结点的祖先是从根到该结点所经分支上的所有结点。反之,以某结点为根的子树中的任一结点都称为该结点的子孙。
树的其他相关概念如下:
(1) 结点的层次从根开始定义起,根为第一层,根的孩子为第二层
(2) 结点的深度:指从根结点到该结点的路径长度
(3) 树的层:位于相同深度的所有结点的集合叫做树的层,如下图所示,B、C和D具有相同的层。根结点位于0层
树的层
(4) 森林(Forest)是m(m≥0)颗互不相交的树的集合。对树中每个结点而言,其子树的集合即为森林。

二叉树

定义

二叉树是n(n≥0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。如下图所示:
二叉树示意图
二叉树的特点如下:
(1) 每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。注意:不是只有两颗子树,而是最多有两颗子树。没有子树或者有一颗子树皆可。
(2) 左子树和右子树是有顺序的,次序不能任意颠倒
(3) 即使树中某结点只有一颗子树,也要区分它是左子树还是右子树。如下图所示,树1和树2是同一棵树,但它们却是不同的二叉树。
左子树和右子树
二叉树的五种基本形态如下:
▷空二叉树
▷只有一个根结点
▷根结点只有左子树
▷根结点只有右子树
▷根结点既有左子树又有右子树

特殊的二叉树又分为满二叉树和完全二叉树。下面对其进行具体的概述
◆ 满二叉树:在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子结点都在同一层中,这样的二叉树称为满二叉树。
【注意】
① 叶子只能出现在最下一层,出现在其它层就不可能达成平衡
② 非叶子结点的度一定是2
③ 在同样深度的二叉树中,满二叉树的结点个数最多,叶子树最多
满二叉树
◆ 完全二叉树:对一颗具有n个结点的二叉树按层序编号,如果编号为i(1≤i≤n)的结点与同样深度的满二叉树中编号为i的结点,在二叉树中位置完全相同,则这颗二叉树称为完全二叉树,如下图所示,其次,按层序编号相同的结点完全二叉树的所有结点与同样深度的满二叉树是以一对应的。
完全二叉树
【注意】
① 叶子只能出现在最下二层
② 最下层的叶子一定集中在左边连续位置
③ 如果结点度为1,则该结点只有左子树,即不存在只有右子树的情况

◆ 二叉树的性质如下:
(1) 性质1 :二叉树第i层上的结点数目最多为2i-1(i≥1)
(2) 性质2:深度为k的二叉树至多有2k-1个结点(k≥1)
(3) 性质3:在任意一颗二叉树中,若终端结点的个数为n0,度为2的结点树为n2,则n0=n2+1

二叉树的存储和遍历
▶ 二叉树的存储
● 顺序存储
顺序存储对树这种一对多的关系结构实现起来较为困难,但是二叉树是一种特殊的树,由于它的特殊性,使得用顺序存储结构也可实现
二叉树的顺序存储结构就是用一维数组存储二叉树中的结点,并且结点的存储位置,即数组的下标要能体现结点之间逻辑关系,如双亲和孩子的关系,左右兄弟的关系等等。
首先了解完全二叉树的顺序存储,如下图所示:
完全二叉树
将这颗二叉树存入到数组中,相应的下标对应其相应的位置,如下图所示:
完全二叉树数组存储
由上图分析可知完全二叉树的优越性。由于它定义严格,所以用顺序结构也可以表现二叉树的结构。
当然对于一般的二叉树,尽管层序编号不能反映逻辑关系,但是可以将其按完全二叉树编号,只不过,把不存在的结点设置为“^”而已,如下图所示:
普通二叉树的数组存储
但是由上图也可知,这是对存储空间的浪费,所以,顺序存储结构一般只用于完全二叉树。

● 链式存储
二叉树每个结点最多有两个孩子,所以设计为一个数据域和两个指针是比较合理的做法,我们称这样的链表叫做二叉链表。结点结构如下图所示:
二叉树结点结构
其中data是数据域,lchild和rchild都是指针域,分别存放指向左子树和右子树的指针,如下图所示:
二叉树链式存储
二叉树链式存储主要实现代码如下:

public class BindaryTreeNode{
  private int data;
  private BindaryTreeNode left;
  private BindaryTreeNode right;
  public int getDate(){
      return data;
  }
  public void setDate(){
     this.data=data;
  }
  public BindaryTreeNode getLeft(){
     return left;
  }
  public void setLeft(BindaryTreeNode left){
     this.left=left;
  }
  public BindaryTreeNode getRight(){
     return right;
  }
  public void setRight(BindaryTreeNode right){
     this.right=right;
  }
}

▶ 二叉树的遍历
访问树中所有结点的过程叫做树遍历。在遍历过程中,每个结点只能被处理一次,尽管其有可能被访问多次。我们已经知道,在线性数据结构(如链表、栈、队列)中,数据元素是以顺序方式被访问的,但是在树结构中,有多种不用的方式。树遍历类似在树中进行搜索操作,遍历的目标是按某种特定的顺序访问树中的所有结点,且遍历过程中所有的结点均需要处理,而搜索操作在指定结点时就会停止。
二叉树的遍历时指从根结点出发,按照某种次序依次访问二叉树中的所有结点,使得每个结点被访问一次且仅被访问一次。访问其实是要根据实际的需要来确定具体做什么,如对每个结点进行相关计算,输出打印等。在这里我们假设就是输出结点的数据信息。
二叉树遍历次序不同于线性结构,最多也就是从头至尾、循环、双向等简单的遍历方式。树的结点之间不存在唯一的前驱后继关系,在访问一个结点后,下一个被访问的结点面临着不同的选择。
下面介绍三种遍历方法
● 前序遍历
二叉树的前序遍历规则是指若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
前序遍历的定义如下:
① 访问根结点
② 按前序遍历方式遍历左子树
③ 按前序遍历方式遍历右子树
二叉树的定义使用递归的方式,所以实现遍历算法也可以采用递归。
二叉树的前序遍历算法如下:

public void PreOrderTraverse(TreeNode node){
   if(node!=null)
   {
       System.out.peintln("["+node.value+"]");
       PreOrderTraverse(node.left_Node);
       PreOrderTraverse(node.right_Node);
   }
}

假设现在有一颗二叉树T,如下图所示,这树已经用二叉链表结构存储在内存当中。那么调用PreOrderTraverse(T)时,遍历顺序为:ABDHECFIG
二叉树T
时间复杂度为0(n),空间复杂度为0(n)

● 中序遍历
中序遍历算法和前序遍历算法在方式上只是顺序上的差异,在中序遍历中,根结点的访问在两颗子树的遍历中间完成。
中序遍历的定义如下:
① 按中序遍历方式遍历左子树
② 访问根结点
③ 按中序遍历方式遍历右子树
二叉树的中序遍历算法如下:

public void InOrderTraverse(TreeNode node){
   if(node!=null)
   {
       InOrderTraverse(node.left_Node);
       System.out.peintln("["+node.value+"]");
       InOrderTraverse(node.right_Node);
   }
}

调用InOrderTraverse(T)时,遍历顺序为:DHBEAIFCG

● 后序遍历
在后序遍历中,根结点的访问是在其两颗子树都遍历完成后进行的。
后序遍历的定义如下:
① 按后序遍历方式遍历左子树
② 按后序遍历方式遍历右子树
③ 访问根结点
二叉树的后序遍历算法如下:

public void PostOrder(TreeNode node){
   if(node!=null)
   {
       PostOrder(node.left_Node);
       PostOrder(node.right_Node);
       System.out.peintln("["+node.value+"]");

   }
}

调用PostOrder(T)时,遍历顺序为:HDEBIFGCA

猜你喜欢

转载自blog.csdn.net/My_ben/article/details/82492019