很久很久之前,我曾经转载过一个由前序遍历和中序遍历来构建二叉树的文章(http://www.cnblogs.com/ITEagle/archive/2010/01/17/1650138.html),但是我最近在准备保研的各种复试的时候,再去写这道题的时候,依然非常的痛苦,几次都不能一次性写对。于是我决定再自己写一篇文章,来分析这个简单的算法,我写的应该和上篇文章有很大不同的,因为那篇文章我完全看不进去。
首先描述算法:
1.在前序遍历中确定根节点,然后在中序遍历中确定左右子树。
2.回到前序遍历,分别确定左右子树的根节点。
3.如此递归,直到完成二叉树的构建。
4.以后序遍历打印二叉树
很明显,这个要用递归来实现。构建二叉树和打印二叉树是两个递归例程。算法很简单,根据算法写出C语言实现也不是什么难事,但是还是有很多需要注意的地方,如果这些地方记住如何写了,能一次写对这个程序,就比较容易了。
#include <stdio.h> #include <stdlib.h> typedef struct node { char data; struct node *lchild; struct node *rchild; }NODE; #define NUM 7 #define LEFT 0 #define RIGHT 1 #define EMPTY 101 char pred[]="ABDECFG"; //前序遍历 char inod[]="DBEACGF"; //中序遍历 int findInIno(char c); int findInPre(char c); NODE *createTree(); void create(NODE *root,int leftlen,int rightlen,int part); void print(NODE *root); int findInIno(char c) { int i; for(i=0;i < NUM;i++) { if(c == inod[i]) return i; } return -1; } int findInPre(char c) { int i; for(i=0;i<NUM;i++) { if(c == pred[i]) return i; } return -1; } NODE *createTree() { NODE *root=(NODE*)malloc(sizeof(NODE)); int rootIndex; root->data = pred[0]; rootIndex = findInIno(root->data); create(root,rootIndex,NUM-rootIndex-1,LEFT); create(root,rootIndex,NUM-rootIndex-1,RIGHT); return root; } void main() { NODE* root = createTree(); print(root); getchar(); }
以上是代码的主题部分,对应了算法的大体思路,先建树,然后打印树,难的在于建树和打印的两个递归函数。
void create(NODE *root,int leftlen,int rightlen,int part) { int rootIndex; int preRootIndex = findInPre(root->data); NODE *p = (NODE*)malloc(sizeof(NODE)); if(leftlen==0) root->lchild =(NODE*)EMPTY; if(rightlen==0) root->rchild=(NODE*)EMPTY; if(leftlen==0&&part==LEFT) { return; } if(rightlen==0&&part==RIGHT) { return; } if(part == LEFT) { p->data = pred[preRootIndex+1]; root->lchild = p; preRootIndex = findInIno(root->data); rootIndex = findInIno(p->data); create(p,rootIndex-preRootIndex+leftlen,preRootIndex-rootIndex-1,LEFT); create(p,rootIndex-preRootIndex+leftlen,preRootIndex-rootIndex-1,RIGHT); } if(part == RIGHT) { p->data = pred[preRootIndex+leftlen+1]; root->rchild = p; preRootIndex = findInIno(root->data); rootIndex = findInIno(p->data); create(p,rootIndex-preRootIndex-1,preRootIndex+rightlen-rootIndex,LEFT); create(p,rootIndex-preRootIndex-1,preRootIndex+rightlen-rootIndex,RIGHT); } }
这是建树的递归函数的代码。函数有四个参数,分别代表了当前的根节点(进入函数体之后,我们会先找到下一个根节点,并把这个根节点当作当前根,于是这个参数root代表的结点就成了前一个结点,这只一种表述方式)、左子树长度、右子树长度。part代表当前处理的是左子树还是右子树。选择使用这几个参数的理由是这样的:root传递当前根节点的数据,有part是因为左右子树的处理方式略有不同,左右字数的长度是用来计算下一次递归调用的参数的。
这里非常关键的就是这个递归调用中,如何用当前根节点的坐标和左右字数的长度来计算,下一次调用中左右子树的长度。特别是处理左子树时候,递归调用时leftlen的计算和处理右子树时,rightlen的计算。在最一开始的时候,我利用了数组的开始坐标和结束坐标来计算,这是完全错误的。这里一定要用上一根节点坐标和当前根节点的坐标,以及传递进来的左右子树长度来计算。
另外就是递归收敛的条件。只有当要处理左子树但是左子树长度为0或者处理右子树但右子树长度为0 的时候,才返回上一层调用。
void print(NODE *root) { if(root->lchild == (NODE*)EMPTY &&root->rchild == (NODE*)EMPTY) { printf("%c",root->data); return; } if(root->lchild != (NODE*)EMPTY) print(root->lchild); if(root->rchild != (NODE*)EMPTY) print(root->rchild); printf("%c",root->data); }
这是后序遍历结果打印的函数。也是递归调用。简单的理解为,在左右子树存在的情况下,先打印左子树,再打印右子树,最后打印根节点。若某子树不存在则跳过。递归调用。
需要注意的依然是收敛的条件,打印到叶子结点的时候,不再继续调用,打印叶子结点数据,然后返回上层函数。