您的位置:首页 > 理论基础 > 数据结构算法

白话篇:利用二叉树先序/中序/后序确定二叉树求法分析

2016-05-31 13:49 465 查看

前篇:

二叉树的遍历:

二叉树的遍历是指按照一定次序访问二叉树中所有的节点,并且每个节点仅仅能够访问一次。这也是二叉树最基本的运算。

常用的3钟递归遍历方式:

1.先序遍历,过程:

(1)访问根节点

(2)访问左子树

(3)访问右子树

2.中序遍历,过程:

(1)访问左子树

(2)访问根节点

(3)访问右子树

3.后序遍历:

(1)访问左子树

(2)访问右子树

(3)访问根节点

为了能够更好的理解到这三种遍历方式,我们画图来模拟一下:

对于一颗二叉树:



1.先序遍历结果:ABDGCEF

2.中序遍历结果:DGBAECF

3.后序遍历结果:GDBEFCA

中篇:

1.如何利用先序和中序确定一颗二叉树

首先我们先证明一下可行性:对于任何n(n>0)个不同节点的二叉树,都可以由它的中序序列和先序序列唯一确定:

采用数学归纳法证明:

当n=0时,二叉树为空,结论正确。

假设:节点数小于n的任何二叉树,都可以由它的中序和先序序列唯一确定。

若一棵树具有n个节点,其先序序列为:a0,a1,a2……an-1,中序序列:b0,b1,b2……bn-1。

由先序遍历特性可知 a0必为二叉树的根节点。而且a0必然在中序序列中出现,也就是说中序序列中存在一个节点bk,bk为根节点a0.

由于bk是根节点,根据中序遍历特性,可以知道在中序序列中b0b1……bk-1必然为跟节点bk的左子树的中序序列。bk+1……bn-1必然为根节点bk的右子树的中序遍历。(因为中序遍历先遍历左子树,然后是根节点,最后是右子树)。即:bk的左子树有k个节点,右子树有n-k-1个节点。

根据先序遍历的特性,那么在先序序列中,a0后面的k个节点a1……ak就是左子树的先序遍历,ak+1……an-1就是右子树的先序序列。

以上过程如图:



由此归纳假设:子先序序列a1……ak,和子中序序列b0……bk-1可以唯一的确定根节点a0的左子树,二先序序列的ak+1……an-1和子中序序列bk+1…bn-1可以唯一的确定根节点a0的右子树(因为这里我们虽然只是确定了一步,但是可以通过递归此过程一步步划分,最终确定此二叉树毕竟操作都是相同的)。
本质:先序序列的作用是确定一颗二叉树的根节点(其第一个元素为根节点),中序序列的作用是确定左右子树的中序序列(包括各自的节点个数),进而可以确定左右子树的先序序列。可以将其理解为一个递归的过程因为这些步骤的操作都是重复性的。只要第一步执行了,后面的操作跟着执行此步骤即可得到最后的结果。

模拟:先序序列:ABDGCEF,中序序列:DGBAECF,则构造过程:



代码实现:

typedef struct T //节点
{
char data;
struct T *l,*r;
}T;

T *Change(char *pre, char *in, int n)
{
if (n <= 0)
{
return NULL; //如果n<=0,二叉树为空
}
T *Tree;
Tree = (T *)malloc(sizeof(T)); //为节点申请空间
Tree->data = *pre; //pre为根节点
int k;
char *p;
for (p = in; p < in + n; p++) //在中序中寻找根节点所在的位置
{
if (*p == *pre)
break;
}
k = p - in; //然后得到根节点的左子树有多少个节点
Tree->l = Change(pre + 1, in, k); //递归构造左子树(左子树的父节点都是按照顺序的,只需要pre+1即可,k为左子树的个数)
Tree->r = Change(pre + k + 1, p + 1, n - k - 1);递归构造右子树(右子树的父节点为pre+k+1,中序的右子树为根节点+1,即:p+1,右子树的个数为n-1-k)
return Tree; 返回构造的二叉树的根。
}

2.中序和后序确定二叉树的构造。

首先我们先证明一下可行性:对于任何n(n>0)个不同节点的二叉树,都可以由它的中序序列和后序序列唯一确定:

采用数学归纳法证明:

当n=0时,二叉树为空,结论正确。

假设:节点数小于n的任何二叉树,都可以由它的中序和后序序列唯一确定。

若一棵树具有n个节点,其后序序列为:a0,a1,a2……an-1,中序序列:b0,b1,b2……bn-1。

由后序遍历特性可知 an-1必为二叉树的根节点。而且an-1必然在中序序列中出现,也就是说中序序列中存在一个节点bk,bk为根节点an-1.

由于bk是根节点,根据中序遍历特性,可以知道在中序序列中b0b1……bk-1必然为跟节点bk的左子树的中序序列。bk+1……bn-1必然为根节点bk的右子树的中序遍历。(因为中序遍历先遍历左子树,然后是根节点,最后是右子树)。即:bk的左子树有k个节点,右子树有n-k-1个节点。

根据后序遍历的特性,那么在后序序列中,an-1前面的k个节点ak……an-2就是右子树的后序遍历,a0……ak-1就是左子树的后序序列。



由此归纳假设:子后序序列a0……ak-1,和子中序序列b0……bk-1可以唯一的确定根节点an-1的左子树,子后序序列的ak……an-1和子中序序列bk+1…bn-1可以唯一的确定根节点an-1的右子树(因为这里我们虽然只是确定了一步,但是可以通过递归此过程一步步划分,最终确定此二叉树毕竟操作都是相同的)。

模拟:后序序列:DGBAECF,中序序列:DGBAECF,则构造过程:



代码实现:

typedef struct T
{
char data;
struct T *l,*r;
}T;

T *Change(char *post,char *in, int n) //后序,中序
{
if (n <= 0)
{
return NULL; //节点小于0,树为空
}
T *Tree;
Tree = (T *)malloc(sizeof(T)); //申请空间
Tree->data = *(post + n - 1); //根节点为后序遍历的最后一个节点
int k;
char *p;
for (p = in; p < in + n; p++) // 在中序序列中找到根节点的位置
{
if (*p == *(post + n - 1))
break;
}
k = p - in; //计算左子树有多少个
Tree->l = Change(post, in, k); //递归创建左子树
Tree->r = Change(post + k,p + 1,n-k-1); //递归创建右子树
return Tree; //返回根节点
}

以上代码亲测可行。

后篇:

为什么先序和后序不能唯一的确定一颗二叉树?

先序中序或者中序后序都具有共同点,那就是找到根节点,区分左右子树。在本质上来说,就是将父节点和子节点进行分离,左右子树分离。但是对于前序和后序来说,他永远只能找到的是开始时的根节点。然而在下步的操作中会有多种选择,最后造成的结果就是得到的二叉树不唯一。

我们也可以根据定义上来解释:

后序遍历:

(1)访问左子树

(2)访问右子树

(3)访问根节点

先序遍历,

(1)访问根节点

(2)访问左子树

(3)访问右子树

那么我们可以根据定义找到根节点,但是对于后序,先访问左子树后访问右子树,先序遍历访问完根节点后,会访问做左子树,再访问右子树。他们都是访问完左子树后访问右子树。这就造成了左子树和右子树的分离无法明确。或者说父子分离出现了问题。

中序遍历,过程:

(1)访问左子树

(2)访问根节点

(3)访问右子树

而对于中序先序或者中序后序来说,中序在访问完左子树后,访问了根节点,然后访问了右子树,那么我们就可以根据中序先序或者中序后序得到一颗确定的二叉树。可以完全正确的对父子节点或者左右子树进行分离。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数据结构