白话篇:利用二叉树先序/中序/后序确定二叉树求法分析
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)访问右子树
而对于中序先序或者中序后序来说,中序在访问完左子树后,访问了根节点,然后访问了右子树,那么我们就可以根据中序先序或者中序后序得到一颗确定的二叉树。可以完全正确的对父子节点或者左右子树进行分离。
相关文章推荐
- C#数据结构之顺序表(SeqList)实例详解
- Lua教程(七):数据结构详解
- 解析从源码分析常见的基于Array的数据结构动态扩容机制的详解
- C#数据结构之队列(Quene)实例详解
- C#数据结构揭秘一
- C#数据结构之单链表(LinkList)实例详解
- C#实现获取系统目录并以Tree树叉显示的方法
- 数据结构之Treap详解
- C语言实现输入一颗二元查找树并将该树转换为它的镜像
- 用C语言举例讲解数据结构中的算法复杂度结与顺序表
- C#数据结构之堆栈(Stack)实例详解
- C#数据结构之双向链表(DbLinkList)实例详解
- JavaScript数据结构和算法之图和图算法
- Java数据结构及算法实例:冒泡排序 Bubble Sort
- 纯jsp打造无限层次的树代码
- Java数据结构及算法实例:插入排序 Insertion Sort
- Java数据结构及算法实例:考拉兹猜想 Collatz Conjecture
- java数据结构之java实现栈
- java数据结构之实现双向链表的示例
- Java数据结构及算法实例:选择排序 Selection Sort