树和二叉树
树和二叉树的定义
树形结构-结点之间有分支,具有层次关系
树的定义
树(tree)是n个结点的有限集,树的定义适应递归的定义
n=0,称为空树
n>0 1.有且仅有一个特定称为根(root)的结点
2.其余结点可分为m个互不相交的有限集T1,T2,T3…Tm,其中每一个集合本身又是一棵树,并称为根的子树(subtree)
表示方式
嵌套集合,凹入表示,广义表
树的基本术语
-根节点:非空树中无前驱结点的结点
-结点的 度 :结点拥有的子树数
-树的度:树内各结点的度的最大值
-叶结点:度为0,终端结点
-孩子结点(子节点):一个结点的孩子结点是指这个结点的子树的根节点
-双亲结点(父节点):一个结点的双亲结点是指,若树中某个结点有孩子
结点,则这个结点就成为是该孩子结点的双亲结点
-兄弟结点:指具有同一个双亲的结点
-堂兄弟结点:双亲在同一层次上的接地那互为堂兄弟
-祖先结点:一个结点的祖先结点是指该结点的路径中除该结点之外的所有结点
-树的深度:树中结点的最大层次,从下往上计算
-树的高度:从上往下计算
两者对于同一棵树来说,深度和高度一样,但是对于某个结点而言就不是这样子了
-有序树:是指树中各个结点的所有子树之间,从左到右有严格的次序关系,不能互换
-无序树:与有序树相反,树中各个结点的所有子树之间没有严格的次序关系
-森林:是m(m>=0)棵互不相交的树的集合,删除一棵树的根结点,这棵树就变成了一个森林,反之,在一个森林加上一个根结点,这个森林就变成了一颗树;树一定是森林,森林不一定是树
二叉树的定义
二叉树的结构最简单,规律性最强
可以证明,所有树都能转化为唯一对应的二叉树,不失一般性
二叉树是n(n>=0)个结点的有限集,是由一个根结点和两棵互不相交的分别称作根的左子树和右子树的二叉树组成
1.每个结点做多有两个孩子(二叉树中不存在度大于2的结点)
2.子树有左右之分,其次序不能颠倒
3.二叉树可以是空集合,跟可以有空的左子树或空的右子树
二叉树不是树的特殊情况,它们是两个概念
也就是说二叉树中每个结点位置次序都是固定的,可以是空,但是不可以说它没有位置,而树的结点位置是相对于别的结点来说的,没有别的结点时,它就无所谓左右了
树和二叉树的抽象数据类型定义
ADT BinaryTree{
数据对象D:D是具有相同特性的数据元素的集合
数据关系R:其中每个结点只有一个前驱(除根结点),可以有零个,一个或两个后继,有且仅有一个结点(根结点)没有前驱
基本操作://至少20个!!!!
CreateBiTree(&T,definition),构造操作:按照definition构造二叉树T,其中definition给出二叉树的定义
PreOrderTraverse(T),先根遍历操作:按照先根遍历次序对二叉树T中的每个结点调用函数Visit()一次且至多一次。其中,Visit()是对结点操作的应用函数,一旦Visit()失败,则操作失败
InOrderTraverse(T),中根遍历操作:按照中根遍历次序对二叉树T中的每个结点调用函数Visit()一次且至多一次。其中,Visit()是对结点操作的应用函数,一旦Visit()失败,则操作失败
PostOrderTraverse(T),后根遍历操作:按照后根遍历次序对二叉树T中的每个结点调用函数Visit()一次且至多一次。其中,Visit()是对结点操作的应用函数,一旦Visit()失败,则操作失败
//还有很多,具体看书
}
二叉树的性质和存储结构
二叉树的性质
- 性质1
在二叉树的第i层上至多有2i-1个结点(i>=1) 归纳法证明
至少有一个结点 - 性质2
深度为k的二叉树至多有2k-1个结点(k>=1)
至少有k个结点 - 性质3
对任何一个二叉树T,如果其叶子树为n0,度为2的结点数为n2,则n0=n2+1
二叉树的特殊形式-满二叉树
一个深度为k且有2k-1个结点的二叉树称为满二叉树
- 特点1:每层上的结点数都是最大结点数(即每层都满)
- 特点2:叶子结点全部在最底层
对满二叉树结点位置进行编号
- 编号规则:从根结点开始,自上而下,自左向右
- 每一结点位置都有元素
二叉树的特殊形式-完全二叉树
深度为k的具有n个结点的二叉树,当且仅当每一个结点都与深度为k的满二叉树中的编号为1-n的结点一一对应时,称为完全二叉树(完全二叉树的逻辑结构与满二叉树的前n个结点的逻辑结构相同)
意思就是完全二叉树是由满二叉树从最后一个结点开始连续去掉任意个结点生成的==一定是要连续去掉!!!所以满二叉树是完全二叉树(满二叉树是完全二叉树的一种特例,满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树)
- 特点1:叶子只可能分布在层次最大的两层上
- 特点2:对任意结点,如果右子树的最大层次为i,则其左子树的最大层次必为i或i+1
- 特点3:结点总数为奇数时,度为1的个数为0;结点总数为偶数时,度为1的个数为1.
- 特点4:完全二叉树是具有n个结点的众多二叉树之中深度最小的一棵树
- 特点5:具有n个结点的完全二叉树的深度是[LOG2N]+1,[X]表示不大于x的最大整数
- 特点6:有一棵n个结点的完全二叉树,任一结点i(1<=i<=n)
1.如果i=1,则结点i是二叉树的根,无双亲,如果i>1,则其双亲结点时i/2
2.如果2i>n,则结点i为叶子结点,无左孩子否则其左孩子是结点2i
3.如果2i+1>n,则结点无右孩子,否则,其右孩子是结点2i+1
二叉树的顺序存储结构
实现:按满二叉树的结点层次编号,依次存放二叉树中的数据元素
二叉树的各个结点先按照一定的顺序排列成一个线性序列,再通过这些结点在线性序列中的相对位置,确定二叉树各个结点之间的逻辑关系。
对于一棵非完全二叉树来说,可以现在次树中增加一些并不存在的虚结点使其成为一棵完整的树,然后用与完全二叉树相同的方法对结点进行编号,再将编号为i的结点的值存放到数组下标为i-1的数组单元中。虚结点不放东西或者放0.
- 二叉树顺序存储表示
#define MAXTSIZE 100
Typedef TElemType SqBiTree[MAXTSIZE];//0号单元存储根结点
SqBiTree bt;
优点结构简单查找方便,但是浪费存储空间,特别是二叉树进行插入,删除结点操作时,会引起大量元素的移动。 对于完全二叉树或者满二叉树,这种顺序存储方式非常适合; 对于非完全二叉树来说,存储空间浪费,存储密度小
二叉树的链式存储结构
二叉树链式存储结构中的结点设置三个域:数据域,左孩子域和右孩子域
- 二叉链表存储结构表示
Typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild;//左孩子指针,右孩子指针
}BiNode,*BiTree;
在n个结点的二叉链表中,有
分析:必有2n个链域。除根结点外,每个结点有且仅有一个双亲,所以只会有n-1个结点的链域存放指针,指向非空子女结点
- 三叉链表存储结构表示
Typedef struct TriTNode{
TElemType data;
struct TriTNode *lchild,*parent,*rchild;//左孩子指针,指向双亲指针,右孩子指针
}TriTNode,*TriTree;
遍历二叉树和线索二叉树
若规定先左后右,则有三种情况
DLR-先(根)序遍历
LDR-中(根)序遍历
LRD-后(根)序遍历
先序遍历二叉树的操作定义
若二叉树为空,则空操作,否则
(1)访问根结点
(2)先序遍历左子树
(3)先序遍历右子树
中序遍历二叉树的操作定义
若二叉树为空,则空操作,否则
(1)中序遍历左子树
(2)访问根结点
(3)中序遍历右子树
后序遍历二叉树的操作定义
若二叉树为空,则空操作,否则
(1)后序遍历左子树
(2)后序遍历右子树
(3)访问根结点
根据遍历序列确定二叉树
由二叉树的先序序列和中序序列,或者后序序列和中序序列可以确定唯一一棵二叉树,但是知道先序和后序无法确定
知道两组序列后,先根据遍历操作定义找出根和接下来的每个子树的根然后一步一步往下推,以中序为目标然后一步一步往下画
遍历算法的实现-递归算法
在二叉链表上使用递归方法实现
先序遍历
void PreOrderTraverse(BiTree T)//传入根结点
{
if(T!=NULL)
{
printf("%c".T->data);//也可以用函数visit()自定义函数输出数据域里面的值
PreOrderTraverse(T->lchild);//递归先序遍历左子树
PreOrderTraverse(T->rchild);//递归先序遍历右子树
}
}
中序遍历
void InOrderTraverse(BiTree T)
{
if(T!=NULL)
{
InOrderTraverse(T->lchild);
printf("%c".T->data);
InOrderTraverse(T->rchild);
}
}
后序遍历
void PostOrderTraverse(BiTree T)
{
if(T!=NULL)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild)