在正式介绍二叉树的构造前,我们先来看一下二叉树的一些直观认识,对于同一棵二叉树具有唯一先序序列,中序序列和后序序列。而对于不同的二叉树可能具有不同的先序序列,中序序列和后序序列。
比如我们来看这两个例子:
例1:先序序列为ABC的五棵二叉树
图1
对于图1中的二叉树来说,它们的先序序列都是A B C 。
例2:中序为ACB的五棵二叉树
图2
对于图2中的二叉树来说,它们的中序序列都是A C B 。
通过上面的例1和例2可知,仅由一个先序序列(中序序列,后序序列)是无法确定一棵二叉树的树形的。那我们思考一下,如果给定先序,中序和后续遍历序列中的任意两个,是否可以确定这棵二叉树的树形?又或者我们思考这几个问题:
- 同时给定一棵二叉树的先序序列和中序序列,就能唯一确定这棵二叉树? 答案:是
- 同时给定一棵二叉树的中序序列和后序序列,就能唯一确定这棵二叉树? 答案:是
- 同时给定一棵二叉树的先序序列和后序序列,就能唯一确定这棵二叉树? 答案:不是
对于问题1和问题2我们将通过定理1和定理2来说明。
1. 定理1
定理1:任何 个不同节点的二叉树,都可由它的中序序列和先序序列唯一地确定。
数学归纳法证明:
基础:当n=0时,说明二叉树是一棵空树,中序序列和先序序列都是唯一确定的,结论正确。
假设:设节点数小于n的任何二叉树,都可以由其先序序列和中序序列唯一地确定。
归纳: 已知某棵二叉树具有 个不同节点,其先序序列是 ;中序序列是 。
而先序遍历:“根-左-右”,那么
必定是二叉树的根节点,另外,
必然在中序序列中有对应的节点,假设在中序序列中必有某个
就是根节点
。那么我们可以根据
节点找到
节点,如图3所示:
图3
如图4所示,由于
是根节点,中序遍历“左-根-右”,故中序序列中:
必是根节点
左子树的中序序列,即
的左子树有k个节点。
必是根节点 右子树的中序序列,即 的右子树有n-k-1个节点。
而同样的在先序序列中, 紧跟在根节点 之后的k个节点 是左子树的先序序列, 这n-k-1就是右子树的先序序列。
图4
根据归纳假设,子先序序列 和子中序序列 可以唯一地确定根节点 的左子树,而先序序列 和子中序序列 可以唯一地确定根节点 的右子树。综上所述,这棵二叉树的根节点己经确定,而且其左、右子树都唯一地确定了,所以整个二叉树也就唯一地确定了。
2. 例题1
例:先序ABDGCEF,中序DGBAECF,二叉树?
图5
最后形成的二叉树:
图6
3. 算法实现
现在我们来看一下算法实现的过程:由先序序列和中序序列构造的二叉树的算法具体实现过程可参考图5。
根据定理1我们可以得出图6二叉树的先序序列和中序序列:
图7
构造左子树:
pre+1为先序序列的左子树开始位置的k个字符
in为中序序列的左子树开始位置的k个字符
构造右子树:
pre+k+1为先序序列的右子树开始位置的n-k-1个字符
p+1为中序序列中的右子树开始的位置的n-k-1个字符
算法实现:
/*
参数pre:先序序列
参数in:中序序列
参数n:二叉树的节点个数
*/
BinaryNode *CreateBT1(char *pre, char *in, int n)
{
//如果给定的n小于等于0,则直接返回
if(n <= 0)
{
return NULL;
}
if(pre == NULL || in == NULL)
{
return NULL;
}
//k记录根节点的位置
int k;
BinaryNode *s = NULL;
char *p = NULL;
//创建根节点
s=(BinaryNode *)malloc(sizeof(BinaryNode));
s->data = *pre;
//在中序(in)中找根节点的位置k
for (p=in; p<in+n; p++)
if (*p==*pre)
break;
k=p-in;
//构建左子树和右子树
s->lchild=CreateBT1(pre+1,in,k);
/*
注意:当在构造左子树时,如果k记录的节点位置为0则说明左子树构造完毕,
紧接着就开始构造右子树
*/
s->rchild=CreateBT1(pre+k+1,p+1,n-k-1);
//最后返回构造完成的二叉树的指针
return s;
}
其实这个算法的实现过程完全是按照定理1而推导出来的,具体的二叉树构造过程也是按照图5来的,而要理解这个算法需要对定理1理解,然后单步调试跟着算法走一遍。
4. 定理2
定理2:任何n(n>0)个不同节点的二叉树,都可由它的中序序列和后序序列唯一地确定。
图8
在前面的学习过程中我们已经知道了中序序列中的根节点,左子树和右子树了。而对于后序序列我们知道遍历过程是:遍历左子树,再遍历右子树,访问根节点,因此后序序列中的最后一个节点就是根节点($a_{n-1}$)
。
那么也就能根据根节点 在中序序列中找到对应的根节点 而当我们一旦确定了中序序列中的根节点位置,那么中序序列和后序序列中的左子树中序序列和右子树中序序列的位置和节点个数也都能确定,如图8所示。
5. 例题2
例如:已知中序DGBAECF,后序GDBEFCA, 对应的构造二叉树的过程如图9所示:
图9
根据定理2我们得出二叉树的后序序列和中序序列,如图10所示:
图10
构造左子树:
post为后序序列的左子树开始位置的k个字符
in为中序序列的左子树开始位置的k个字符
构造右子树:
post + k为后序序列的右子树开始位置的n-k-1个字符
p+1为中序序列中的右子树开始的位置的n-k-1个字符
6. 算法实现
其实定理2的算法实现和定理2的算法实现过程是非常类似的,如果你已经看明白了定理1,相信定理2对你来说就很简单了,下面直接把算法贴上。
/*
参数in:中序序列
参数post:后序序列
参数n:二叉树的节点个数
*/
BinaryNode *CreateBT2(char *post , char *in, int n)
{
BinaryNode *s = NULL;
char *p = NULL;
int len = strlen(post);
//k记录根节点位置
int k;
//ch记录根节点
char ch = *(post + n - 1);
if(n <= 0)
{
return NULL;
}
if(in == NULL || post == NULL)
{
return NULL;
}
//用后序序列中最后一个节点来创建根节点
s = (BinaryNode *)malloc(sizeof(BinaryNode));
s->data = ch;
//在中序(in)中找根节点的位置k
for (p=in; p<in+n; p++)
if (*p == ch)
break;
k=p-in;
//构造左子树和构造右子树
s->lchild = CreateBT2(post,in,k);
s->rchild = CreateBT2(post + k , p+1 , n-k-1);
return s;
}