树是由n(n>=0)个结点所构成的有限集合,当n=0时,称为空树。当n>0时,n个结点满足以下条件:
1) 有且仅有一个称为根的结点。
2)其余结点可分为m(m>=0)个互不相交的有限集合,且每个集合又构成一棵树,这课树称为根结点的子树。
树的层次结构体现了数据元素之间具有的层次关系,即对于一颗非空树,其中有且仅有一个没有前驱的结点,这个结点就是根结点,其余结点有且仅有一个前驱,单可以有多个后继。
1)树的结点
树的结点是由一个数据元素及关联子树的边所组成。例如,上图中的a中只有一个结点,b中有3个节点,c中有13个结点。在计算机程序中,结点中的数据元素一般代表着一些实体,节点中的边代表着实体与实体之间的逻辑关系。
2)结点的路径
结点的路径是指从根根结点到该结点所经历的结点和分支的顺序排序。例如,上图中c中结点J的路径是A->D->J。
3)路径的长度
路径的长度是指节点中路径所包含的分支数。例如,上图c中结点J的路径长度为2。
4)结点的度
结点的度是指该结点所拥有子树的数目。例如,上图c中结点A的度为3,C的度为0。
5)树的度
树的度是指书中所有结点的度的最大值。例如,上图a中树的度为0,b中树的度为1,c中树的度为3。
6)叶结点(终端结点)
叶结点是指树中度为0的结点,叶结点也称为终端结点。例如,上图c中C、E、F、H、I、J、K、L、M都是叶结点。
7)分支结点(非终端 结点)
分支结点是指树中度不为0的节点,分支结点也称为非终端结点。树中除叶结点之外的所有结点都是分支结点。
8)孩子结点(子结点)
一个结点的孩子结点是指这个结点的子树的根结点。例如,上图c中结点B、C、D是结点A的孩子结点,或者说结点A的孩子结点是B、C、D。
9)双亲结点(父结点)
一个结点的双亲结点是指:若树中某个结点有孩子结点,则这个结点就称为孩子结点的双亲结点。例如,上图c中结点A是指结点B、C、D的双亲结点。双亲结点和孩子结点也称为是具有互为前驱和后继关系的结点,其中,双亲结点是孩子结点的前驱,而孩子结点是双亲结点的后继。
10)子孙结点
一个结点的子孙结点是指这个结点的所有子树中的任意结点。例如,上图c中结点B的子孙结点有E、F、G、K、L、M。
11)祖先结点
一个结点的祖先结点是指该结点的路径中除此结点之外的所有结点。例如,上图c中结点L的祖先结点有A、B、G。
12)兄弟结点
兄弟结点是指具有同一双亲的结点。例如,上图c中B、C、D是兄弟节点,它们的双亲结点都是结点A。
13)节点的层次
规定树中根结点的层次为0,则其他结点的层次是其双亲结点的层次加1。例如,上图c中节点K的层次数为3,也可称节点K在树中处于第3层上。
14)树的深度
树的深度是指树中所有结点的层次数的最大值加1。例如,上图a中中树的深度为1,b中树的深度为3,c中树的深度为4。
15)有序树
有序树是指树中各结点的所有子树之间从左到右有严格的次序关系,不能互换。也就是说,如果子树的次序不同则对应着不同的有序树。
16)无序树
与有序树相反,无序树是指树中各结点的所有子树之间没有严格的次序关系。
17)森林
森林是指由m(m>=0)课互不相交的树构成的集合。
二叉树是一种特殊的树,它的每个结点最多只有两颗子树,并且这两颗子树也是二叉树。由于二叉树中两颗子树有左、右之分,所以二叉树是有序树。下面给出二叉树的具体定义。
1. 二叉树的定义
二叉树是由n(n>=0)个结点所构成的有限集合。当n=0时,这个集合为空,此时的二叉树为空树;当n>0时,这个集合是由一个根结点和两个互不相交的分别称为左子树和右子树的二叉树构成。
二叉树与树的区别:
1)树中每个结点都可以有多颗子树,而二叉树中的每个结点最多有两颗子树
2)树中的子树是不分顺序的,而二叉树中的子树有严格的左、右之分。
3)二叉树与度小于2等于的树也不同。在二叉树中允许某些结点只有右子树而没有左子树,而在树中,一个结点若没有第一棵子树,则它就不可能有第二棵子树存在。
2. 满二叉树的定义
满二叉树是二叉树的一种特殊形态。如果在一棵二叉树中,它的所有结点或是叶结点,或是左、右子树都非空,并且所有叶结点都在同一层上,则称这可二叉树为满二叉树。
3. 完全二叉树的定义
完全二叉树也是二叉树的一种特殊形态。如果在一棵具有n个结点的二叉树中,它的逻辑结构与满二叉树前n个结点的逻辑结构相同,则成这样的二叉树而完全二叉树。
4. 单分支树的定义
若二叉树的所有结点都没有右孩子或没有左孩子,则此二叉树为单分支树。单分支树又可分为左支树和右支树,其中所有结点都没有右孩子的二叉树称为左支树,所有结点都没有左孩子的树称为右支树。
性质1 二叉树中第i(i>=0)层上的结点树最多为2^i。
性质2 深度为h(h>=1)的二叉树中最多有2^h-1个结点。
性质3 对于任何一棵二叉树,若其叶结点的个数为n0,度为2的节点个数为n2,则有n0=n2+1。
性质4 具有n个结点的完全二叉树,其深度为int(log2n)+1。
性质5 有n个结点的完全二叉树,若从根结点开始自上到下并且按照层次有左向右对结点从0开始变编号,则对于任意一个编号i(0<=i
1)若i=0,则编号为i的节点时二叉树的根结点,它没有双亲;若i>1,则编号为i的结点其双亲的编号为
2)若2i+1>=n,则编号为i的结点无左孩子,否则编号为2i+1的结点就是其左孩子
3)若2i+1>=n,则编号为i的结点无右孩子,否则编号为2i+2的结点就是其右孩子
二叉树的顺序存储结构是指按照某种顺序依次将二叉树中的各个结点的值存放在一组地址连续的存储单元中。由于二叉树是非线性结构的,所以需要将 二叉树的各个结点先按照一定的顺序排列成一个线性序列,再通过这些节点在线性序列中的相对位置,确定二叉树中的各个结点之间的逻辑关系。对于一棵完全二叉树,可以从根结点开始自上而下并按照层次有左向右对结点一次进行编号,然后按照编号顺序依次将其存放在一维数组中,其结点之间的逻辑关系可有性质5中父结点与孩子结点编号之间的关系式计算得到。对于一棵非完全二叉树,可有现在此树中添加一些并不存在的虚结点并使其成为一棵完全二叉树,然后用与完全二叉树相同的方法对结点进行编号,再将编号为i的结点的值存放到数组下表为i的数组单元总,虚结点不存放任何值。
对于满二叉树和完全二叉树,顺序存储是一种最简单、最节省空间的存储方式,而且能是操作简单,所以顺序存储方式非常适合用于满二叉树和完全二叉树。但对于非完全二叉树,由于有“虚结点”的存在从而造成了存储空间的浪费,特别是对右支树来说,若其深度为h,则此右支树只有h个结点,却要为此分配2^h-1个存储单元,这种情况下的存储空间浪费最大。
二叉树的链式存储结构是指将二叉树的各个结点随机地存放在位置任意的内存空间中,各个结点之间逻辑关系通过指针来反映。由于二叉树中的任意一个结点至多只有一个双亲结点和两个孩子结点,所以在用链式存储方式来实现而差事的存储时,可以有两种形式,一种是二叉链表存储结构,另一种是三叉链表存储结构。在二叉链表机构中,二叉树中的每一个结点设置有3个域:数据域、左孩子域和右孩子域,其中,数据域用来存放结点的值;左、右孩子域分别用来存放该结点的左、右孩子结点的存储地址,三叉链表结构是指在二叉链表结构中增加一个父结点域,该域用来存放此结点的父结点的存储地址。
三叉链表存储结构既方便查找孩子结点,又便于查找双亲结点,但它相对于二叉链表存储结构来说,增加了存储空间的开销。因此,二叉链式存储结构是二叉树中最常用的存储结构。
二叉链表存储结构的结点类描述如下:
package tree;
public class BiTreeNode {
public Object data;// 结点的数据域
public BiTreeNode lchild, rchild;// 左、右孩子域
public BiTreeNode() {
this(null);
}
public BiTreeNode(Object data) {
this(data, null, null);
}
public BiTreeNode(Object data, BiTreeNode lchild, BiTreeNode rchild) {
this.data = data;
this.lchild = lchild;
this.rchild = rchild;
}
}
3. 二叉链式存储结构下二叉树类的描述
二叉树的遍历是指沿着某条搜索路径对二叉树中的结点进行访问,使得每个结点均被访问一次,而且仅被访问一次。其中“访问”的含义较为广泛,它可以是输出二叉树中的结点信息,也可以是对结点进行任何其他处理。
由二叉树的结构特点,可以将二叉树划分为3个部分:根结点、左子树和右子树;其次,二叉树的所有结点都是有层次之分。因此,对于一棵二叉树来说,它有3条搜索路径,分别是:先上后下、先左(子树)后右(子树)和先右(子树)后左(子树)。如果规定用D、L和R分别表示访问根结点、处理左子树和处理右子树,则可得到二叉树的7种遍历方法:层次遍历、DLR、LDR、LRD、DRL、RDL和RLD。现在给出前面4种遍历方法的定义。
1)层次遍历操作
若二叉树为空,则为空操作;否则,先访问第0层的根结点,然后从左到右依次访问第1层的每一个结点,以此类推,当第i层的所有结点访问完成后,再从左到右依次访问第i+1层的每一结点,直到最后一层的所有结点都访问完为止。
根据根的访问的先后次序不同,这里将DLR称为先根或先序遍历;LDR称为中根或中序遍历;LRD称为后根或后序遍历。由于二叉树是递归定义的,所以DLR、LDR、LRD3种遍历方法给出的定义也都是递归定义的。
2)先根遍历(DLR)操作
若二叉树为空,则为空操作;否则
➀访问根结点
➁先根遍历左子树
➂先根遍历右子树
3)中根遍历(LDR)操作
若二叉树为空,则为空操作;否则
➀中根遍历左子树
➁访问根结点
➂中根遍历右子树
4)后根遍历(LRD)操作
若二叉树为空,则为空操作;否则
➀后根遍历左子树
➁后根遍历右子树
➂访问根结点
采用不同方法对二叉树进行遍历可以得到二叉树结点的不同线性序列。
/**
* 先根遍历操作实现的递归算法
*/
public void preRootTraverse(BiTreeNode t) {
if (t != null) {
System.out.print(t.data);// 访问根结点
preRootTraverse(t.lchild);// 先根遍历左子树
preRootTraverse(t.rchild);// 先根遍历右子树
}
}
/**
* 中根遍历操作实现的递归算法
*/
public void inRootTraverse(BiTreeNode t) {
if (t != null) {
inRootTraverse(t.lchild);// 中根遍历左子树
System.out.print(t.data);// 访问根结点
inRootTraverse(t.rchild);// 中根遍历右子树
}
}
/**
* 后根遍历操作实现的递归算法
*/
public void postRootTraverse(BiTreeNode t) {
if (t != null) {
postRootTraverse(t.lchild);// 后根遍历左子树
postRootTraverse(t.rchild);// 后根遍历右子树
System.out.print(t.data);// 访问根结点
}
}
先根遍历是指每次进入一层递归调用时先访问根结点,然后依次对它的左、右子树执行递归调用;中根遍历是指在执行完左子树的递归调用后再访问根结点,然后对右子树执行递归调用;后根遍历则是指执行完左、右子树的递归调用后,最后访问根结点。
将递归算法转换成非递归算法有两种方式:一种是直接转换法,不需要回溯;另一种是间接转换法,需要回溯。前置使用一些变量保存中间结果,递归过程用循环结构来替代;后者需要利用栈保存中间结果,故引入一个栈操作,并依照递归算法执行过程中编译栈的工作原理,得到相应的非递归算法。二叉树便利操作的非递归算法的实现采用的就是间接间接转换法。
1)先根遍历操作的实现
先根遍历的递归过程是指先访问根结点,再沿着该结点的左子树向下一次访问其左子树的根结点,直到最后访问的结点都是无左子树为止,再继续依次向上先根遍历根结点的右子树。
先根遍历的非递归过程则需要借助一个栈来记载当前被访问结点右孩子结点,以便遍历完一个结点的左子树后能顺利地进入这个结点的右子树继续进行遍历。
实现先根遍历操作的非递归算法的主要思想:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点就先访问该结点,并将该结点的非空右孩子结点压栈。当左子树结点访问完成后,从栈顶弹出结点的右孩子结点,然后用上述相同的方法去遍历该结点的右子树,以此类推,直到二叉树中所有结点都被访问为止。其主要操作过程可描述如下;
1)创建一个栈对象,根结点入栈。
2)当栈为非空时,将栈顶结点弹出栈内并访问该结点。
3)对当前访问结点的非空左孩子结点相继依次访问,并将当前访问结点的非空右孩子结点压入栈内。
4)重复执行步骤2)和3),直到栈为空为止。
public void preRootTraverse() {
BiTreeNode t = root;
if (t != null) {
LinkStack s = new LinkStack();// 构造栈
s.push(t);// 根结点入栈
while (!s.isEmpty()) {
t = (BiTreeNode) s.pop();// 移除栈顶结点,并返回其值
System.out.print(t.data);// 访问根结点
while (t != null) {
if (t.lchild != null) {// 访问左孩子
System.out.print(t.lchild.data);// 访问结点
}
if (t.rchild != null) {// 右孩子非空入栈
s.push(t.rchild);
}
t = t.lchild;
}
}
}
}
2)中根遍历操作的实现
中根遍历的非递归过程也要借助一个栈来记载遍历过程中所经历的而未被访问所有结点,以遍历完一个结点的左子树后能顺利地返回到它的父结点。
实现后根遍历操作的非递归算法的主要思想:从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点就依次压栈,直到二叉树中最左下的结点压栈为止,然后从栈中弹出栈顶结点并对其进行访问,访问完后再进入该结点的右子树并用上述同样的方法去遍历该结点的右子树,以此类推,直到二叉树中所有结点都被访问为止。其主要操作过程可描述如下;
1)创建一个栈对象,根结点入栈。
2)当栈为非空时,将栈顶结点的非空左孩子相继入栈。
3)栈顶结点出栈并访问非空栈顶结点,并使该栈顶结点的非空右孩子结点入栈。
4)重复执行步骤2)和3),直到栈为空为止。
public void inRootTraverse() {
BiTreeNode t = root;
if (t != null) {
LinkStack s = new LinkStack();// 构造站节点栈结点
s.push(t);// 根结点入栈
while (!s.isEmpty()) {
while (s.peek() != null) {// 将栈顶结点的左孩子结点相继入栈
s.push(((BiTreeNode) s.peek()).lchild);
}
s.pop();// 空结点退栈
if (!s.isEmpty()) {
t = (BiTreeNode) s.pop();// 移除栈顶结点,并返回其值
System.out.print(t.data);// 访问结点
s.push(t.rchild);// 结点的右孩子入栈
}
}
}
}
3)后根遍历操作的实现
由于后根遍历是先处理左子树,后处理右子树,最后才访问根结点,所以在遍历搜索过程中也是从二叉树的根结点出发,沿着该结点的左子树向下搜索,在搜索过程中每遇到一个结点判断该结点是否第一次经过,若是,则不立即访问,而是将该结点入栈保存,遍历该结点的左子树;当左子树遍历完毕后再返回到该结点,这时还不能访问该结点,而是应继续进入该结点的右子树进行遍历;当左、右子树均遍历完毕后,才能从栈顶弹出该结点并访问它。由于在决定栈顶结点是否能访问时,需要知道该结点的右子树是否被遍历完毕,因此为解决这个问题,在算法中引入一个布尔型的访问编辑变量flag和一个结点指针p。其中,flag用来标记当前栈顶结点是否被访问,当值为true时,表示栈顶结点已被访问;当值为false时,表示当前栈顶结点未被访问,指针p指向当前遍历过程中最后一个被访问的结点。若当前栈顶结点的右孩子结点是空,或者就是p指向的结点,则表明当前结点的右子树已遍历完毕,此时就可以访问当前栈顶结点。其操作的实现过程描述如下:
1)创建一个栈对象,根结点入栈,p赋初始值null。
2)若栈非空,则栈顶结点的非空左孩子相继入栈
3)若栈非空,查看栈顶结点,若栈顶结点的右孩子为空,或者与p相等,则将栈顶结点弹出并访问它,同时使p指向该结点,并置flag值为true;否则,将栈顶结点的右孩子压入栈,并置flag值为false。
4)若flag值为true,则重复执行步骤3);否则,重复执行步骤2)和3),直到栈为空为止。
public void postRootTraverse() {
BiTreeNode t = root;
if (t != null) {
LinkStack s = new LinkStack();// 构造链栈
s.push(t);// 根结点入栈
Boolean flag;// 访问标记
BiTreeNode p = null;// p指向刚被访问的结点
while (!s.isEmpty()) {
while (s.peek() != null) {// 将栈顶结点的左孩子相继入栈
s.push(((BiTreeNode) s.peek()).lchild);
}
s.pop();// 空结点出栈
while (!s.isEmpty()) {
t = (BiTreeNode) s.peek();// 查看栈顶元素
if (t.rchild == null || t.rchild == p) {
System.out.print(t.data);// 访问结点
s.pop();// 移除栈顶元素
p = t;// p指向刚被访问的结点
flag = true;// 设置访问标记
} else {
s.push(t.rchild);// 右孩子结点入栈
flag = false;// 设置未被访问标记
}
if (!flag) {
break;
}
}
}
}
}
4)层次遍历操作实现
层次遍历操作的实现过程中需要使用一个队列作为辅助的存储结构,这里使用了链队列类LinkQueue来创建一个队列对象l。在遍历开始时,首相将根结点入队,然后每次从队列中取出队首元素进行处理,每处理一个结点,都是先访问该结点,再按照从左到右的顺序把它的孩子结点一次入队。这样,上一层的结点总排在下一层结点的前面,从而实现了二叉树的层次遍历。其操作的实现过程描述如下:
1)创建一个队列对象
2)若队列非空,则将队首节点出队并访问该结点,再将该结点的非空左、右孩子结点依次入队。
3)重复执行步骤2),知道队列为空为止。
public void levelTraverse() {
BiTreeNode t = root;
if (t != null) {
LinkQueue l = new LinkQueue();// 构造队列
l.offer(t);// 根结点入队
while (!l.isEmpty()) {
t = (BiTreeNode) l.poll();
System.out.print(t.data);// 访问结点
if (t.lchild != null) {// 左孩子非空,入队列
l.offer(t.lchild);
}
if (t.rchild != null) {// 右孩子非空,入队列
l.offer(t.rchild);
}
}
}
}
下面来测试下二叉树BiTree类
package debug;
import tree.BiTree;
import tree.BiTreeNode;
public class DebugBiTree {
public BiTree createBiTree() {
BiTreeNode d = new BiTreeNode('D');
BiTreeNode g = new BiTreeNode('G');
BiTreeNode h = new BiTreeNode('H');
BiTreeNode e = new BiTreeNode('E', g, null);
BiTreeNode b = new BiTreeNode('B', d, e);
BiTreeNode f = new BiTreeNode('F', null, h);
BiTreeNode c = new BiTreeNode('C', f, null);
BiTreeNode a = new BiTreeNode('A', b, c);
return new BiTree(a);// 构造根结点为a的二叉树
}
public static void main(String[] args) {
DebugBiTree debugBiTree = new DebugBiTree();
BiTree biTree = debugBiTree.createBiTree();
BiTreeNode root = biTree.root;// 取得树的根结点
// 测试先根遍历
System.out.print("(递归)先根遍历序列为:");
biTree.preRootTraverse(root);
System.out.println();// 输出换行
System.out.print("(非递归)先根遍历序列为:");
biTree.preRootTraverse();
System.out.println();
// 测试中根遍历
System.out.print("(递归)中根遍历序列为:");
biTree.inRootTraverse(root);
System.out.println();
System.out.print("(非递归)中根遍历序列为:");
biTree.inRootTraverse();
System.out.println();
//测试后根遍历
System.out.print("(递归)后根遍历序列为:");
biTree.postRootTraverse(root);
System.out.println();
System.out.print("(非递归)后根遍历序列为:");
biTree.postRootTraverse();
System.out.println();
//测试层次遍历
System.out.print("层次遍历序列为:");
biTree.levelTraverse();
System.out.println();
}
}
练习1 二叉树的查找:编写算法完成在二叉树中查找值为x的结点的操作。
思路:1)若二叉树为空,则不存在这个接单,返回空值;否则,将根结点的值与x进行比较,若相等,则返回该结点。
2)若根结点的值与x不相等,则在左子树中进行查找,若找到,则返回找到的结点。
3)若在左子树中没有找到值为x的结点,则继续在右子树中进行查找,并返回查找结果。
public BiTreeNode searchNode(BiTreeNode t, Object x) {
BiTreeNode node;
if (t != null) {
if (t.data.equals(x)) {// 对根结点进行判断
return t;
} else if ((node = searchNode(t.rchild, x)) != null) {
return node;
} else if ((node = searchNode(t.rchild, x)) != null) {
return node;
}
}
return null;
}
练习2 计算二叉树中结点的个数
使用先根遍历的递归的方法
public int countNode(BiTreeNode t) {
int count = 0;
if (t != null) {
count++;// 根结点增加1
count += countNode(t.lchild);// 加上左子树节点数
count += countNode(t.rchild);// 加上右子树节点数
}
return count;
}
使用递归模型方法
| 0 (t为空)
f(t) = | 1 (t为叶结点)
| max(f(t.lchild),f(t.rchild))+1 (其他情况)
public int countNode2(BiTreeNode t) {
if (t == null) {
return 0;
} else {
return 1 + countNode2(t.lchild) + countNode2(t.rchild);
}
}
使用层次遍历的非递归方法
public int countNode3(BiTreeNode t) {
int count = 0;
if (t != null) {
LinkQueue l = new LinkQueue();
l.offer(t);
while (!l.isEmpty()) {
t = (BiTreeNode) l.poll();
count++;
if (t.lchild != null) {
l.offer(t.lchild);
}
if (t.rchild != null) {
l.offer(t.rchild);
}
}
}
return count;
}
练习3 求二叉树的深度
求二叉树的深度,方法是先求出左子树的深度,再求出右子树的深度,二叉树的深度就是左子树的深度和右子树的深度中的最大值加1。
后根遍历方法
public int getDepth(BiTreeNode t) {
if (t != null) {
int lDepth = getDepth(t.lchild);// 左子树的深度
int rDepth = getDepth(t.rchild);// 右子树的深度
return 1 + (lDepth > rDepth ? lDepth : rDepth);// 返回左子树的深度和右子树深度中最大值加1
}
return 0;
}
使用递归模型方法
| 0 (t为空)
f(t) = | 1 (t为叶结点)
| max(f(t.lchild),f(t.rchild))+1 (其他情况)
public int getDepth2(BiTreeNode t) {
if (t == null) {
return 0;
} else if (t.lchild == null && t.rchild == null) {
return 1;
} else {
int depth = Math.max(getDepth2(t.lchild), getDepth2(t.rchild));
return 1 + depth;
}
}
练习4 判断两棵二叉树是否相等。
若两棵树相等,则只有两种情况:一种情况是这两棵二叉树都为空;另一种情况则是当两棵二叉树都为非空树,两棵树的根结点、左子树和右子树都分别对应相等。
先根遍历方法:
public boolean isEqual(BiTreeNode t1, BiTreeNode t2) {
if (t1 == null && t2 == null) {// 同时为空
return true;
}
if (t1 != null && t2 != null) {// 同时非空进行比较
if (t1.data.equals(t2.data)) {// 根结点的值是否相等
if (isEqual(t1.lchild, t2.lchild)) {// 左子树是否相等
if (isEqual(t1.rchild, t2.rchild)) {// 右子树是否相等
return true;
}
}
}
}
return false;
}
递归模型方法:
| true (t1和t2都为空)
f(t1,t2) = | t1.data==t2.data && f(t1.lchild,t2.lchild) && f(t1.rchild,t2.rchild) (t1和t2都非空)
| false (其他情况)
public boolean isEqual2(BiTreeNode t1, BiTreeNode t2) {
if (t1 == null && t2 == null) {
return true;
} else if (t1 != null && t2 != null) {
return (t1.data.equals(t2.data) && (isEqual2(t1.lchild, t2.lchild)) && (isEqual2(
t1.lchild, t2.lchild)));
} else {
return false;
}
}
先根遍历序列或后根遍历序列能反映双亲与孩子结点之间的层次关系,而中根遍历能反映兄弟结点之间的关系。所以,已知一种二叉树的遍历序列是不能唯一确定一棵二叉树的。只有已知先根和中根遍历序列,或中根和后根遍历序列,才能唯一确定一棵二叉树。而已知先根和后根遍历序列也是无法唯一确定一棵二叉树。
1. 由先根和中根遍历序列建立一棵二叉树
由于二叉树是由具有层次关系的结点所构成的非线性结构,而且二叉树中的每个结点的两棵子树具有左、右次序之分,所以要建立一棵二叉树,就必须明确:结点与双亲结点和孩子结点之间的层次关系;兄弟结点的左右次序关系。创建步骤:
1)区先根遍历序列的第一个结点作为根结点;
2)在中根遍历序列中寻找根结点,确定根结点在中根遍历序列中的位置,假设为i(0<=i<=count-1),其中,count为二叉树遍历序列中结点的个数。
3)在中根遍历序列中确定:根结点之前的i个结点序列构成左子树的中根遍历序列,根结点之后的count-i-1,剩下的count-i-1个结点序列构成右子树的先根遍历序列。
4)在先根遍历序列中确定:根结点之后的i个结点序列构成左子树的先根遍历序列,剩下的count-i-1个结点构成右子树的先根遍历序列;
5)由步骤3)和4)又确定了左、右子树的先根和中根遍历序列,接下来可以用上述同样的方法来确定左、右子树的根结点,以此递归就可建立唯一的一棵二叉树。
实现上述建立二叉树的算法,需要引入5个参数:preOrder、inOrder、preIndex、inIndex和count。其中,参数preOrder是整棵树的先根遍历序列;inOrder是整棵树的中根遍历序列;preIndex是先根遍历序列在preOrder中的开始位置;inIndex是中根遍历序列在InOrder中的开始位置;count表示树中结点的个数。
public BiTree(String preOrder, String inOrder, int preIndex, int inIndex,
int count) {
if (count > 0) {
char r = preOrder.charAt(preIndex);// 取先根遍历序列中的第一个结点作为根结点
int i = 0;
for (; i < count; i++) { // 寻找根结点在中根遍历序列中的位置
if (r == inOrder.charAt(inIndex + i)) {
break;
}
}
root = new BiTreeNode(r);// 建立树的根结点
root.lchild = new BiTree(preOrder, inOrder, preIndex + 1, inIndex,
i).root;
root.rchild = new BiTree(preOrder, inOrder, preIndex + i + 1,
inIndex + i + 1, count - i - 1).root;
}
}
2. 由标明空子树的先根遍历序列建立一棵二叉树
已知二叉树的先根遍历序列是不能够唯一确定一棵二叉树,如果能够在先根遍历序列中加入每一个结点的空子树信息,则可明确二叉树中结点与双亲、孩子与兄弟间的关系,因此就可唯一确定一棵二叉树。
按标明空子树“#”的先根遍历序列来建立一棵二叉树的主要操作步骤:
从标明空子树信息的先根遍历序列中依次读入字符,若读入的字符是“#”,则建立空子树;否则
1)建立根结点
2)继续建立树的左子树
3)继续建立树的右子树
3. 由完全二叉树的顺序存储结构建立其二叉链式存储结构
对于一棵顺序存储的完全二叉树,由二叉树的性质5可知,第0个字符是根结点,对于第i个字符,它的双亲是第[(i-1)/2]个字符,若它有左孩子,则它的左孩子是第2i+1个字符;若它有右孩子,则它的右孩子是第2i+2个字符。所以利用性质5,根据完全二叉树的顺序存储结构,可以建立完全二叉树的二叉链式存储结构。