首先解决一个问题,int a[10]; change(a); 而子函数则写为void change(int a[]) 此时相当于传入的是指针,此时可以在子函数中实现对数组a[]的修改!
先序遍历,就是先遍历根结点,再遍历左子树,然后是右子树;中序遍历,就是先遍历左子树,再遍历根结点,再遍历右子树;后序遍历,就是先遍历左子树,再遍历右子树,最后遍历根结点。
可以根据先序遍历序列和中序遍历序列,建立起树的结构,从而得到后序遍历的序列。这里先说一下建立结构体的方法,在struct 前加上 typedef 这样 typedef struct node{}n; 此时n 是 struct node 的别名,在定义时可以使用 n n1 的这种方式,实际上着这种方式对c是合适的,但是对于c++来说是多此一举的,c中要struct node n而c++中不用typedef也可以直接使用node n。
首先观察一个二叉树:
它的先序遍历为12435,中序遍历为42135,后序遍历为42531,层序遍历为12345.
要注意的是Insert()函数起到了不断向树添加结点的作用,具体的要求应该按照题目可能是比较大小之类的,这里仅仅是相当于随机的向左右子树插入结点,Insert(NODE* &T, int x)中的T是引用,才能用create真正的从NULL的修改,否则因为T是一个指针,只能修改它所指向的那些值。层序遍历类似于BFS广度优先搜索,利用了一个队列,根结点就插入队尾,访问过元素后,再将其左右子树依次放入队尾,每次从队首出队一个结点进行访问。
下面的代码包含了递归和非递归形式的先序,中序,后序遍历以及非递归形式的层序遍历,完成了一棵完整的访问过程,以上面的树为例子,对于后序遍历,要注意的是每次访问的结点一定是在上一次访问的结点是它的直接右子树或者为空的情况,代码为:
#include<cstdio>
#include<queue>
#include<stack>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct NODE{
NODE *lchild;
NODE *rchild;
int c;
}Tree[50];//Tree只在create中被用到,这样就不用new了
int loc;
char str[50];
int flag = 0;
queue<NODE*> q;//存入的是指针类型,队列保存的只是原元素的副本,无法直接修改,但当为地址可以对指向的值进行的修改
//创建一个新的结点,返回的是指针即地址,主要是定义左右子树指向的结点
NODE *create(int ch)
{
Tree[loc].c = ch;
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}
void preOrder(NODE *T)
{
if(T == NULL)
return;
printf("%d ", T->c);
preOrder(T->lchild);
preOrder(T->rchild);
}
void inOrder(NODE *T)
{
if(T == NULL)
return;
inOrder(T->lchild);
printf("%d ", T->c);
inOrder(T->rchild);
}
void postOrder(NODE *T)
{
if(T == NULL)
return;
postOrder(T->lchild);
postOrder(T->rchild);
printf("%d ", T->c);
}
//层序遍历非递归形式,传入的参数是根结点指针
//层序遍历与广度优先搜索比较类似
void layerOrder(NODE *root)
{
q.push(root);
while(!q.empty())
{
NODE *T = q.front();
q.pop();
printf("%d ", T->c);//访问值
if(T->lchild != NULL)
q.push(T->lchild);
if(T->rchild != NULL)
q.push(T->rchild);
}
}
//非递归先序遍历,只对传入的根结点进行处理
//先将当前结点的右子树入栈,再将左子树入栈即可
void none_preOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
stk.push(root);
//和层序遍历的队列的思想有点类似
while(!stk.empty())
{
NODE *T = stk.top();
stk.pop();
printf("%d ", T->c);
if(T->rchild != NULL)
stk.push(T->rchild);
if(T->lchild != NULL)
stk.push(T->lchild);
}
}
//对于中序遍历,从根结点开始一直到最左边的结点
void none_inOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
NODE *T = root;
while(T != NULL || !stk.empty())
{
//T可能出现为NULL的情况
while(T != NULL)
{
stk.push(T);
T = T->lchild;
}
if(!stk.empty())
{
T = stk.top();
printf("%d ", T->c);
stk.pop();
T = T->rchild;//对右子树实现中序遍历
}
}
}
//对于后序遍历,每次访问的结点必然是在访问过它的右结点之后直接访问,右节点必然为空
//一定是紧接着它的右节点后被访问,当然可能为空
//或者没有左右子树才被访问
void none_postOrder(NODE *root)
{
if(root == NULL)
return;
stack<NODE*> stk;
NODE *cur, *pre = NULL;//注意这个写法
cur = root;
while(cur)
{
stk.push(cur);
cur = cur->lchild;
}//最左入栈
while(!stk.empty())
{
cur = stk.top();
stk.pop();
if(cur->rchild == pre || cur->rchild == NULL)//同样满足叶子结点
{
printf("%d ", cur->c);
pre = cur;
}
else
{
stk.push(cur);
cur = cur->rchild;//由上面的判断可知rchild一定不为NULL,叶子结点已经被访问过
while(cur)
{
stk.push(cur);
cur = cur->lchild;
}
}
}
}
//按照从从左到右的顺序,优先向左子树插入,下面的这种方式最终会把数据都插入到根结点的左子树上
//注意&T是引用,实现对T本身的create()而不是仅对T指向的值的修改
void Insert(NODE* &T, int x)
{
//只有当前为NULL的时候才能插入
if(T == NULL)
{
T = create(x);
flag = 1 - flag;
return;
}
if(flag == 1)
{
Insert(T->lchild, x);
}
else if(flag == 0)
{
Insert(T->rchild, x);
}
}
//对于寻找插入位置,也可以用递归的形式,当此时的结点指针为NULL时,就可以调用create(ch)了
//对于查找相应元素并进行修改,也可以进行递归,用先序遍历的方式即可
int main()
{
NODE* root = NULL;
loc = 0;
Insert(root, 1);
Insert(root, 2);
Insert(root, 3);
Insert(root, 4);
Insert(root, 5);
printf("先序遍历: \n");
preOrder(root);
printf("\n");
printf("非递归先序遍历: \n");
none_preOrder(root);
printf("\n");
printf("中序遍历: \n");
inOrder(root);
printf("\n");
printf("非递归中序遍历: \n");
none_inOrder(root);
printf("\n");
printf("后序遍历: \n");
postOrder(root);
printf("\n");
printf("非递归后序遍历: \n");
none_postOrder(root);
printf("\n");
printf("层序遍历: \n");
layerOrder(root);
printf("\n");
printf("Tree: ");
for(int i = 0; i < 5; i++)
printf("%d ", Tree[i].c);
return 0;
}
运行结果为:
接下来看题目描述:
题目描述:
二叉树的前序、中序、后序遍历的定义:
前序遍历:对任一子树,先访问跟,然后遍历其左子树,最后遍历其右子树;
中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;
后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根。
给定一棵二叉树的前序遍历和中序遍历,求其后序遍历(提示:给定前序遍历与中序遍历能够唯一确定后序遍历)。
输入:
两个字符串,其长度 n 均小于等于 26。
第一行为前序遍历,第二行为中序遍历。二叉树中的结点名称以大写字母表示:A,B,C....最多 26 个结点。
输出:
输入样例可能有多组,对于每组测试样例,输出一行,为后序遍历的字符串。
样例输入:
ABC
BAC
FDXEAG
XDEFAG
样例输出:
BCA
XEDGAF
题目的解决思路是,从根结点开始,逐渐开始搭建起树的结构,这也是最难的部分,要通过先序遍历和中序遍历的序列规律进行搭建, 在先序遍历中找到根结点,再在中序遍历中找到该结点,该结点左侧的即为该结点的左子树,右侧的即为右子树,再根据先序遍历找到左子树的根结点,继续遍历,直到最后无左子树和右子树递归跳出。下面的程序要注意的是,我们这里没有采用动态产生 node 的方式(malloc),而是预先分配了一个静态数组Tree.
代码为:
#include<cstdio>
#include<iostream>
#include<stdlib.h>
#include<algorithm>
#include<functional>
#include<queue>
#include<string.h>
using namespace std;
struct Node {
Node *lchild;
Node *rchild;
char v;
}Tree[50];
//不用 int loc = 0; 因为会有很多组数据,初始化要在main()中定义
int loc;
Node *create()
{
Tree[loc].lchild = Tree[loc].rchild = NULL;
return &Tree[loc++];
}
//全局变量,所以在build函数中不用传入
char str1[30], str2[30];
void postOrder(Node *T)
{
//先遍历左子树,再遍历右子树,不用else
if (T->lchild != NULL)
postOrder(T->lchild);
if (T->rchild != NULL)
postOrder(T->rchild);
printf("%c",T->v);
}
//主要是使每个结点都有对应的左右子树
//s1,e1对应的是先序遍历的起始结束下标,s2,e2对应的是中序遍历的起始结束下标
Node *build(int s1, int e1, int s2, int e2)
{
Node *T = create();
T->v = str1[s1];
int idx;
for(int i = s2; i <= e2; i++)
if (str2[i] == str1[s1])
{
idx = i;
break;
}
//当有左子树时,再次进行递归,下面的非常重要,最好画图来考虑一下
//左子树的结点数目是 idx - s2.
if (idx != s2)
T->lchild = build(s1 + 1, s1 + idx - s2, s2, idx - 1);
if (idx != e2)
T->rchild = build(s1 + idx - s2 + 1, e1, idx + 1, e2);//去掉左子树的数目才是先序遍历右子树的起点
return T;
}
int main()
{
//直接用str1,不用取地址
while (scanf("%s", str1) != EOF)
{
scanf("%s", str2);
loc = 0;
Node *root = create();
root = build(0, strlen(str1) - 1, 0, strlen(str2) - 1);
postOrder(root);
printf("\n");
}
return 0;
}
运行结果为:
ABC
BAC
BCA
FDXEAG
XDEFAG
XEDGAF