树和二叉树(一)

444 阅读10分钟

树与二叉树的概念

树的定义:树(Tree)是n(n >= 0)个结点的有限集。

  • 若n = 0 ,则称为空树

  • 若 n > 0,则它满足如下条件

    1.有且仅有一个特定的称为根(Root)的结点

    1. 其余结点可分为m(m >= 0)个互不相交的优先级T1,T2,T3……Tm,其中每一个集合本身又是一棵树,并称为根的子树(SubTree)

2021-11-16 21-22-07 的屏幕截图.png

所以树的义实际是一个递归的定义。

我们需要知道一些树的基础术语:
树的深度:树中结点的最大层次 结点的度:结点拥有的子树数 树的度:树内各节点的度的最大值 [度为0的终端结点被称作叶子结点,而其余结点除根节点外被称作内部结点]
结点的子树的根称为该结点的孩子,该结点称为孩子的双亲
兄弟结点:有共同的双亲 堂兄弟结点:双亲在同一层的结点 结点的祖先:从根到该节点所经分支上的所有结点 结点的子孙:从某结点为根的子树中的任一结点 森林:是m(m >= 0)棵互不相交的树的集合[一棵树可以看做一个特殊的森林]

二叉树

二叉树是结构最简单,规律性最强的树,而且所有树都能装为唯一对应的二叉树。所以二叉树在树结构的应用中起着非常重要的作用,因为对二叉树的许多算法比较简单,二任何数都可以与二叉树互相转化,这样就解决了树的存储结构机器运算中存在的复杂性。

二叉树的定义:二叉树是n(n >= 0)个结点的有限集,它或者是空集,或者是由一个根结点及两棵互不相交的分别称作这个根的左子树和右子树的二叉树组成的。

需要注意的是,二叉树并不是树的特殊情况,他们是两个概念。二叉树结点的子树要区分左子树和右子树,即便只有一棵子树也要进行区分,说明他是左子树还是右子树。二树,当结点只有一个孩子时,就无需区分它是左还是右的次序。这是二者的主要区别。(也就是说二叉树每个结点位置或者说次序都是固定的,可以是空,但是不可以说它没有的位置,二树的结点位置是相对别的结点来说的,没有别的结点时,他就无所谓左右了)

树和二叉树的性质

抽象数据类型定义

二叉树的抽象数据类型定义右非常多的操作,其中初始化和遍历较为重要。其他的当个参考,如下:

ADT BinaryTree{   
    数据对象D:D是具有相同特性的数据元素的集合  
    数据关系R: 
       若D=Φ,则R=Φ,称BinaryTree为空二叉树; 
       若D≠Φ,则R={H},H是如下二元关系; 
     (1)在D中存在惟一的称为根的数据元素root,它在关系H下无前驱; 
     (2)若D-{root}≠Φ,则存在D-{root}={D1,Dr},且D1Dr =Φ; 
     (3)若D1≠Φ,则D1中存在惟一的元素x1,<root,x1>∈H,且存在D1上的关系H1 ⊆H;若Dr≠Φ,则Dr中存在惟一的元素xr,<root,xr>∈H,且存在上的关系Hr ⊆H;H={<root,x1>,<root,xr>,H1,Hr}; 
     (4)(D1,{H1})是一棵符合本定义的二叉树,称为根的左子树;(Dr,{Hr})是一棵符合本定义的二叉树,称为根的右子树。 

   基本操作: 
   InitBiTree( &T ) 
     操作结果:构造空二叉树T。

   DestroyBiTree( &T ) 
     初始条件:二叉树T已存在。 
     操作结果:销毁二叉树T。 

   CreateBiTree( &T, definition ) 
     初始条件:definition给出二叉树T的定义。 
     操作结果:按definiton构造二叉树T。 

   ClearBiTree( &T ) 
     初始条件:二叉树T存在。 
     操作结果:将二叉树T清为空树。 

   BiTreeEmpty( T ) 
     初始条件:二叉树T存在。 
     操作结果:若T为空二叉树,则返回TRUE,否则返回FALSE。 

   BiTreeDepth( T ) 
     初始条件:二叉树T存在。 
     操作结果:返回T的深度。 

   Root( T ) 
     初始条件:二叉树T存在。 
     操作结果:返回T的根。 

   Value( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:返回e的值。 

   Assign( T, &e, value ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:结点e赋值为value。 

   Parent( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:若e是T的非根结点,则返回它的双亲,否则返回“空”。 

   LeftChild( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:返回e的左孩子。若e无左孩子,则返回“空”。 

   RightChild( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:返回e的右孩子。若e无右孩子,则返回“空”。 

   LeftSibling( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:返回e的左兄弟。若e是T的左孩子或无左兄弟,则返回“空”。 

   RightSibling( T, e ) 
     初始条件:二叉树T存在,e是T中某个结点。 
     操作结果:返回e的右兄弟。若e是T的右孩子或无右兄弟,则返回“空”。 

   InsertChild( T, p, LR, c ) 
     初始条件:二叉树T存在,p指向T中某个结点,LR01,非空二叉树c与T不相交且右子树为空。 
     操作结果:根据LR01,插入c为T中p所指结点的左或右子树。p所指结点的原有左或右子树则成为c的右子树。 

   DeleteChild( T, p, LR ) 
     初始条件:二叉树T存在,p指向T中某个结点,LR01。 
     操作结果:根据LR01,删除T中p所指结点的左或右子树。 

   PreOrderTraverse( T, visit() ) 
     初始条件:二叉树T存在,Visit是对结点操作的应用函数。 
     操作结果:先序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。 

   InOrderTraverse( T, visit() ) 
    初始条件:二叉树T存在,Visit是对结点操作的应用函数。 
     操作结果:中序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。 

   PostOrderTraverse( T, visit() ) 
     初始条件:二叉树T存在,Visit是对结点操作的应用函数。 
     操作结果:后序遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。 

   LevelOrderTraverse( T, visit() ) 
     初始条件:二叉树T存在,Visit是对结点操作的应用函数。 
    操作结果:层次遍历T,对每个结点调用函数Visit一次且仅一次。一旦visit()失败,则操作失败。 

}ADT BinaryTree

满二叉树和完全二叉树

满二叉树:一棵深度为k且有(2^k)-1个结点的二叉树 [编号规则:自上而下,自左而右]

特点:
  • 每一层上的结点数都是最大结点数,每一节点位置都有元素(每一层都满,2^(i-1)个结点)
  • 叶子结点全在最底层

2021-11-17 21-57-59 的屏幕截图.png

完全二叉树:深度为k的具有n个结点的二叉树,当且仅当其每一个结点斗鱼深度为k的满二叉树中编号为1~n一一对应时,称之为完全二叉树

特点:

  • 叶子只能分布在层次最大的两层上(倒数第二层与最后一层)
  • 对任一结点,如果其右子树最大层次为i,则其左子树最大层次必为i或者i+1

二叉树的性质

  • 在二叉树的第i层至多有2^(i-1)个结点(i >= 1) [第i层至少1个结点]
  • 深度为k的结点至多有(2^k)-1个结点 [深度为k的二叉树最多有k个结点]
  • 对任何一棵二叉树T,如果其叶子树为n0,度为1的结点数为n1,度为2的结点数为n2,则n0 = n2 + 1 [证明略]
  • 具有n个结点的完全二叉树的深度为[log(2)n]+1[log(2)n]+1 ([x]称作x的底,表示不大于x的最大整数)
  • 如果对一棵有n个结点的完全二叉树(深度为[log(2)n]+1)的结点按层序编号(从第一层到第[log(2)n]+1层,每层从左到右),则对任一结点i(1<=i<=n)有:
    1. 如果i=1,则结点i是二叉树的根,无双亲:若i>1,则其双亲是结点[1/2]
    2. 若2i>n,则结点i为叶子结点,无左孩子;否则,其做孩子是2i
    3. 若2i+1>n,则结点i无右孩子;否则,其右孩子是结点2i+1

树和二叉树的实现

二叉树的顺序储存

顺序存储用依旧是数组去实现,按满二叉树的结点层次编号,依次放二叉树中的数据元素,结点为空时就置0.

//二叉树顺序存储
#define MAXSIZE 100
Typedef TelemType SqBoTree[MAXSIZE];
SqBiTree bt;

顺序存储有明显的缺点,当最坏情况下,深度为k的且只有k个结点的单支树需要长度为2^(k-1)的一维数组,几遍不是最坏情况也是很容易浪费空间的。所以顺序储存比较适合满二叉树和完全二叉树。

二叉树的链式储存

二叉树每个结点都可以有两个孩子,所以可以又叫做二叉链表,即两个制作分别指向左右孩子

typedef struct BiNode{
    TElemType data;
    struct BiNode *lchild,*rchild;
}BiNode,*BiTree;

但是在我们需要找某个结点双亲的时候,二叉链表就不太方便了,所以我们需要三叉链表:

typedef struct TriTNode{
    TElemType data;
    struct TriTNode *lchild,*rchild,*parent;
}TriTNode,*TriTTree;

遍历二叉树

三种遍历方法

遍历的定义:顺着耨意义奥搜索路径寻访二叉树中的结点,使得每一个结点被访问一次且仅被访问一次(又称周游)

“访问“的含义很广,可以是对结点进行各种处理:输出结点的信息,修改结点的数据等,但要求这种访问不破坏原来的数据结构

遍历的目的:得到树中所以普结点的一个现行排列
遍历的用途:他是树结构插入,删除,修改,查找和排序运算的前提,是所有二叉树运算的基础,核心
一共有三种遍历方式(始终先左后右):

  • DLR——先序遍历
  • LDR——中序遍历
  • LRD——后序遍历

2021-11-19 13-21-38 的屏幕截图.png 自己可以找一棵树是哟下各种遍历方法:

2021-11-19 13-35-04 的屏幕截图.png

由遍历方法确定二叉树

若二叉树中各结点的值均不同,则二叉树结点的先序序列,中序序列和后序序列东营市唯一的。所以我们可以通过二茶树的遍历序列确定唯一的一棵二叉树。
比如由二叉树的先序序列和中序序列可以确定唯一一棵二叉树,又比如树的后序序列和中序序列可以确定唯一一棵二叉树。(必须需要中序序列做参考)