-
树的定义和基本术语
树型结构是一类重要的非线性数据结构,一个对多个,其中以树和二叉树最为常用。
树的定义:
树(Tree)是n(n≥0)个结点的有限集,它或为空树(n = 0);或为非空树,对于非空树T:
(1)有且仅有一个称之为根(Root)的结点;
(2)除根结点以外的其余结点可分为m(m>0)个互不相交的有限集T1, …, Tm , 其中每一个集合Ti本身又是一棵树,并且称为根的子树(SubTree)。
根(Root):即根结点(没有前驱)
叶子(Leaf):即终端结点(没有后继)
森林(Forest):指m棵不相交的树的集合 (例如删除A后的子树个数)
有序树:结点各子树从左至右有序,不能互换(左为第一)
无序树:结点各子树可互换位置
双亲(Parent):即上层的那个结点(直接前驱)
孩子(Child):即下层结点的子树的根(直接后继)
兄弟(Sibling):同一双亲下的同层结点(孩子之间互称兄弟)
堂兄弟:即双亲位于同一层的结点(但并非同一双亲)
祖先:即从根到该结点所经分支的所有结点
子孙:即该结点下层子树中的任一结点
结点:即树的数据元素
结点的度(degree):结点挂接的子树数(孩子数【0,1,2】)
结点的层次:从根到该结点的层数(根结点算第一层)
终端结点:即度为0的结点,即叶子
分支结点:即度不为0的结点(也称为内部结点)
树的度:所有结点度中的最大值
树的深(高)度:指所有结点中最大的层数
-
二叉树
二叉树(Binary Tree)是 n (n≥0) 个结点所构成的集合,它或为空树(n = 0);或为非空树,对于非空树T:满足
(1)有且仅有一个称之为根的结点;
(2)除根结点以外的其余结点分为
两个互不相交的子集T1和T2,分别称为T的
左子树和
右子树,且T1和T2本身又都是二叉树。
二叉树的性质:
性质1: 在二叉树的
第i层上至多有
2i-1个结点
性质2: 深度为k的二叉树至多有
2k-1个结点
性质3: 对于任何一棵二叉树,若度为2的结点数有n2个,则叶子数n0必定为n2+1 (即n0=n2+1)
完全二叉树和满二叉树:
满二叉树:一棵深度为k 且有2^k -1个结点的二叉树。(特点:每层都“充满”了结点)
完全二叉树:深度为k 的,有n个结点的二叉树,当且仅当其每一个结点都与深度为k 的满二叉树中编号从1至n 的结点一一对应,见下图。
特点:(1)叶子结点只可能在层次最大的两层上出现
(2)对任一结点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l+1。
两者之间的差别:满二叉树是叶子一个也不少的树,而完全二叉树虽然前n-1层是满的,但最底层却允许在右边缺少连续若干个结点。满二叉树是完全二叉树的一个特例。
性质4: 具有n个结点的完全二叉树的深度必为
ëlog2nû +1(向下取整)
性质5: 对完全二叉树,若从上至下、从左至右编号, 则编号为 i 的结点, 其左孩子(若存在)编号必为2i, 其右孩子(若存在)编号必为2i+1;其双亲的编号必为
ëi/2û
。
-
二叉树的存储结构
1.顺序存储结构
用一组地址连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素,即将完全二叉树上编号为i的结点元素存储在如上定义的一维数组中下标为i-1的分量中。(0表示不存在此结点)
顺序存储结构的缺点:仅适用于完全二叉树,因为一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)却需要长度为
2
k
-1
的一维数组。
2.链式存储结构
二叉树的结点:
二叉链表:二叉树的结点由一个数据元素和分别指向其左、右子树的两个分支构成,则表示二叉树的链表中的结点至少包括3个域:数据域和左、右指针域。
三叉链表:为了便于找到结构中的双亲再在结点结构中增加一个指向其双亲结点的指针域
二叉树的二叉链表存储表示:
typedef struct BiNode{
TElemType data;
struct BiNode *lchild,*rchild; //左右孩子指针
}BiNode,*BiTree;
二叉树的二叉链表存储表示:
typedef struct TriTNode
{ TelemType data;
struct TriTNode *lchild,*rchild;
struct TriTNode *parent;
}TriTNode,*TriTree;
习题:
-
遍历和搜索二叉树
一、遍历二叉树
遍历定义:指按某条搜索路线遍访每个结点且不重复(又称周游)。
遍历用途:它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。
遍历二叉树的递归算法:
(1)先序遍历(DLR):根——左——右
Status PreOrderTraverse(BiTree T){
if(T==NULL)
return OK; //空二叉树
else{
cout<<T->data; //访问根结点
PreOrderTraverse(T->lchild); //递归遍历左子树
PreOrderTraverse(T->rchild); //递归遍历右子树
}
}
(2)中序遍历(LDR):左——根——右
Status InOrderTraverse(BiTree T){
if(T==NULL)
return OK; //空二叉树
else{
InOrderTraverse(T->lchild); //递归遍历左子树
cout<<T->data; //访问根结点
InOrderTraverse(T->rchild); //递归遍历右子树
}
}
(3)后序遍历(LRD):左——右——根
Status PostOrderTraverse(BiTree T){
if(T==NULL)
return OK; //空二叉树
else{
PostOrderTraverse(T->lchild); //递归遍历左子树
PostOrderTraverse(T->rchild); //递归遍历右子树
cout<<T->data; //访问根结点
}
}
时间复杂度和空间复杂度都是O(n);
二叉树遍历算法的应用:
1.计算二叉树遍历总数:
* 如果是空树,则结点个数为0;
* 否则,结点个数为左子树的结点个数+右子树的结点个数再+1。
int NodeCount(BiTree T){
if(T == NULL ) return 0;
else
return NodeCount(T->lchild)+ NodeCount(T->rchild) + 1;
}
2.计算二叉树叶子结点总数
* 如果是空树,则叶子结点个数为0;
* 否则,为左子树的叶子结点个数+右子树的叶子结点个数。
int LeadCount(BiTree T){
if(T==NULL) return 0; //如果是空树返回0
if (T->lchild == NULL && T->rchild == NULL)
return 1; //如果是叶子结点返回1
else
return LeafCount(T->lchild) + LeafCount(T->rchild);
}
结论:
若二叉树中各结点的值均不相同,则:由二叉树的前序序列和中序序列,或由其后序序列和中序序列均能唯一地确定一棵二叉树,但由前序序列和后序序列却不一定能唯一地确定一棵二叉树。
习题:已知一棵二叉树的
中序序列和
后序序列分别是
BDCEAFHG 和
DECBHGFA,请画出这棵二叉树。
解析:
①由后序遍历特征,根结点必在后序序列尾部(A);
②由中序遍历特征,根结点必在其中间,而且其左部必全部是左子树子孙(BDCE),其右部必全部是右子树子孙(FHG);
③继而,根据后序中的DECB子树可确定B为A的左孩子,根据HGF子串可确定F为A的右孩子;以此类推。
中序遍历递归算法执行过程中递归工作栈的状态:
(1)记录中包括两项:递归调用的语句编号;指向根结点的指针,当栈顶记录中的指针非空时,应遍历左子树,即指向左子树根的指针进栈。
(2)若栈顶记录中的指针值为空,则应退至上一层,若是从左子树返回,则应访问 当前层即栈顶记录中指针所指向的根结点。
(3)若从右子树返回则表明当前层的遍历结束,应继续退栈。
从另一个角度看,这意味着遍历右子树不再需要保护当前层的根指针,可直接修改栈顶记录中的指针即可。
//中序遍历二叉树的非递归算法(左根右)
Status InOrderTraverse(BiTree,Status(*Visit)(TElemType e){
//采用二叉链表存储结构,Visit是对数据元素操作的应用函数,每个数据元素调用函数Visit.
InitStack(S); Push(S,T);//根指针进栈
while(!StackEmpty(S)){
while(GetTop(S,p)&&p)Push(S,p->lchild);//向左走到尽头
Pop(S,p); //空指针退栈
if(!StackEmpty(S)){//访问结点,向右走一步
Pop(S,p);if(!Visit(p->data))return ERROR;
Push(S,p->rchild);
}
}
return OK;
}
例:按先序序列建立二叉树的二叉链表,再读入字符
Status CreateBiTree(BiTree &T){
scanf(&ch);//读入字符
if(ch==' ')T=NULL;
else{
if(!(T=(BiTNode*)malloc(sizeof(BiTNode))))exit(OVERFLOW);
T->data=ch;//生成根结点
CreateBiTree(T->lchild);//构造左子树
CreateBiTree(T->rchild);//构造右子树
}
return OK;
}
二叉链表的缺点:二叉链表空间效率这么低,存在空闲区
改进:使用空闲区存放当前结点的直接前驱和后继等线索,以加快查找速度。
二、搜索化二叉树
遍历二叉树实际上是对一个非线性结构进行线性化操作。但是当以二叉链表作为存储结构时,只能找到结点的左右孩子信息,而不能直接得到结点在任意序列中的前驱和后继信息(只能遍历得到)。
那么如何保存这种遍历过程中的信息呢?
1.在每个结点上增加两个指针域fwd和bkwd,分别只是结点在任意次序遍历时得到的前驱和后继信息。这样能大大的降低结构的存储密度。
2.使用n个结点的二叉链表中的n+1个空链域。
定义:
线索:指向结点前驱和后继的指针
线索链表:加上线索二叉链表
线索二叉树:加上线索的二叉树(图形式样)
线索化:对二叉树以某种次序遍历使其变为线索二叉树的过程