在描述二叉树的前中后序遍历之前,我们要先了解什么是树。根据教材上的定义,树是一个n(n>0)的有限集,且在任意一颗非空树中,有且仅有一个特定的结点称为根,且当 n>1 时,其余结点可分为m(m>0)个互不相交的有限集,其中每一个本身又是一棵树,并且称为根的子树。简单来说,树除了根以外的每一个结点都有且仅有一个前驱结点,称为双亲结点,可以有0个、1个、2个甚至多个后继结点,称为孩子结点。双亲结点相同的结点,称为兄弟结点。结点拥有的子树个数,称为子树的度,而树的度,是树中各结点度的最大值。
而二叉树,又是树中的特例,二叉树是度为2,且子树有左右之分的树。因此可以说二叉树是度为2的树,但不能说度为2的数是二叉树,因为二叉树的子树有左右之分,左子树与右子树即使有一方不存在,另一方仍为左子树或右子树。
所以从二叉树的数据结构就可以看出,对它的构建和遍历并不像顺序表、链表等线性表那样方便。不管是创建二叉树还是遍历二叉树,都需要使用函数的递归调用。
比如二叉树的构建,就需要使用递归调用。如果输入的数字大于零,则构建一个结点并将该数字存入结点的数据域中,然后开始构建该结点的左子树或右子树,当输入小于零的数字时,另该结点的值为空,然后返回,继续执行未完成的函数:
void creattree(tnode** t) {
int a;
cin >> a;
if (a < 0)
*t = NULL;
else {
*t = new tnode;
(*t)->data = a;
creattree(&(*t)->ltnode);
creattree(&(*t)->rtnode);
}
}
而相较于构建,遍历的操作更多样,可以根据根节点的输出顺序分为前序遍历、中序遍历和后序遍历。对于这三种遍历,显然较为方便的是利用函数递归,只调整数据输出的位置即可:
void preorder(tnode* t) {
if (t) {
cout << t->data;
preorder(t->ltnode);
preorder(t->rtnode);
}
}
void midorder(tnode* t) {
if (t) {
midorder(t->ltnode);
cout << t->data;
midorder(t->rtnode);
}
}
void lastorder(tnode* t) {
if (t) {
lastorder(t->ltnode);
lastorder(t->rtnode);
cout << t->data;
}
}
当然,也可以不使用函数递归的方式进行二叉树的后序遍历,但是代码较为复杂。为了实现后序遍历的非递归算法,可以使用两个栈,其中一个栈存放第一次经过的结点,另一个栈存放第二次经过的点,如果第一个栈中不存在第二个栈中结点的子孙结点,则将第二个栈中的该结点输出。
int notin(SqStack sq1, SqStack sq2) {
for (int i = 0; i < sq1.len; i++) {
if ((*(sq2.top - 1))->rtnode == *(sq1.base + i))
return 0;
}
return 1;
}
void lastorder2(tnode* t) {
SqStack sq1, sq2;
sq1.base = new tnode*;
sq1.top = sq1.base;
sq2.base = new tnode*;
sq2.top = sq2.base;
while (t || sq1.len >= 0 || sq2.len >= 0) {
if (t) {
push(sq1, t);
t = t->ltnode;
continue;
}
else {
while (1) {
t = pop(sq1);
push(sq2, t);
t = t->rtnode;
if (!t) {
t = pop(sq2);
cout << t->data;
if (sq2.len > 0)
while (notin(sq1, sq2) && sq2.len > 0) {
t = pop(sq2);
cout << t->data;
if (sq2.len == 0)
break;
}
}
else {
break;
}
}
}
}
}