这里附上题目链接:[NOIP2001 普及组] 求先序排列题解。
~~手动分割~~
我将整棵二叉树的根称为根结点,子树的根称为父结点,以便于区分。
因为题目给出了二叉树的中序排列和后序排列,根据中序排列+后序排列唯一确定一棵二叉树的定理,可
对于不知道如何重构二叉树的读者,可以参考我的另一篇文章二叉树的重构与遍历。
AC代码
#include
#include
#include
struct node//node表示树的结点
{
char data;//data意为数据域
struct node *left_child;//left_child意为左子树的父结点
struct node *right_child;//right_child意为右子树的父结点
};
//postorder为当前子树的后序排列末尾元素的地址,inorder为当前子树的中序排列首元素的地址
//length为当前子树的后序排列的长度,parent_node为当前要插入的结点的双亲结点的地址
struct node *reconstruct_binary_tree2(char *postorder,char *inorder,int length,struct node *parent_node,int n)
{
int left_length=0,right_length=0;//left_length为当前根结点左子树的先序排列的长度,right_length为当前根结点左子树的先序排列的长度
struct node *cur_inser_node=(struct node *)malloc(sizeof(struct node));//先给要插入二叉树的结点分配内存空间
char *root_node=postorder;//当前子树的前序排列的后序排列的末尾元素即为当前子树的根结点
char *border=inorder;//border为中间变量指针,接下来会用它来记录当前子树的根结点在中序排列中的位置,从而得到当前子树的左子树和右子树
(cur_inser_node)->data=*postorder;//将父结点所代表的元素存入结构体的数据域
(cur_inser_node)->left_child=NULL;
(cur_inser_node)->right_child=NULL;
if(n==0)//n=0说明此时插入的结点是二叉树的树根(也就是整棵二叉树的根结点),它是没有双亲结点的
{
parent_node=cur_inser_node;//之所以要进行这一步赋值是防止在下一个if判断中出现对值为0的指针的访问
}
if(parent_node->left_child==NULL||parent_node->right_child==NULL)//若当前要插入的结点的双亲结点的地址为空
{
if(n==1)//n=1说明正在重构parent_node的左子树
{
parent_node->left_child=cur_inser_node;//让双亲结点向左指向当前要插入的结点
}
else if(n==2)//n=2说明正在重构parent_node的右子树
{
parent_node->right_child=cur_inser_node;//让双亲结点向右指向当前要插入的结点
}
}
if(length==1)//若前子树的先序排列的长度为1,说明当前要插入的结点没有左右子树,此时不需要再继续重构
{
return cur_inser_node;
}
while(*border!=*root_node)//寻找当前子树的父结点在中序排列中的位置
{
border++;
}
left_length=(int)(border-inorder);//left_length为当前结点的左子树的先序排列的长度
right_length=(int)(length-left_length-1);//左子树和右子树先序排列的长度之和为length-1,因为在此之前已经往二叉树中插入了一个结点
if(left_length>0)//重构左子树
{
reconstruct_binary_tree2(postorder-1-right_length,inorder,left_length,cur_inser_node,1);
}
if(right_length>0)//重构右子树
{
reconstruct_binary_tree2(postorder-1,inorder+left_length+1,right_length,cur_inser_node,2);
}
return cur_inser_node;
}
//先序遍历
int preorder_traversal(struct node *root_node)
{
if(root_node==NULL)
{
return 0;
}
printf("%c",root_node->data);
preorder_traversal(root_node->left_child);
preorder_traversal(root_node->right_child);
return 0;
}
int main()
{
int length;
struct node* root_node=NULL;
char inorder_sequence[20],postorder_sequence[20],b;
//输入数据
scanf("%s%c",inorder_sequence,&b);
scanf("%s%c",postorder_sequence,&b);
length=strlen(inorder_sequence);
//重构二叉树
root_node=reconstruct_binary_tree2(postorder_sequence+length-1,inorder_sequence,length,root_node,0);
//先序遍历
preorder_traversal(root_node);
return 0;
}
在思路1中,我用二叉链表作存储结构重构二叉树,在通过对其进行先序遍历输出先序排列。但重构这一步其实是多余的。或者说,如果要通过递归来对二叉树进行先序遍历的话,就必须要重构二叉树(使用数组或二叉链表来存储二叉树的结构)。
但是我们知道,先序遍历的遍历顺序是根结点、左子树、右子树,而子树的后序排列的末尾元素是该子树的父结点,在知道了父结点元素后,可以在中序排列中分离出该父结点的左子树和右子树。知道了左子树和右子树的两个排列后,又可以分离出左子树和右子树的根(注意:结点的子树的根为该结点的孩子结点),不断递归地执行下去,直到分离出所有的结点。
所以对于此题,更优化的思路是不重构二叉树(即不使用任何数据结构存储二叉树的结构),通过题目输入的两个排列,不断地分离出二叉树的结点、结点的左子树以及父结点的右子树,直到分离出所有的结点。
#include
#include
#include
struct node//node表示树的结点
{
char data;//data意为数据域
struct node *left_child;//left_child意为左子树的父结点
struct node *right_child;//right_child意为右子树的父结点
};
//inorder为当前子树的中序排列首元素的地址,postorder为当前子树后序排列末尾元素的地址,length为当前子树的排列长度
int preorder_traversal(char *inorder,char *postorder,int length)
{
char *border=inorder,*t=inorder;
int left_length=0,right_length=0;
printf("%c",*postorder);//输出父结点
if(length==1)//若当前父结点没有左右子树
{
return 0;
}
while(*border!=*postorder)
{
border++;
}
left_length=(int)(border-t);//left_length为当前父结点的左子树的排列长度
right_length=length-1-left_length;//right_length为当前结点的右子树的排列长度
if(left_length>0)
{
preorder_traversal(inorder,postorder-1-right_length,left_length);//遍历左子树
}
if(right_length>0)
{
preorder_traversal(inorder+1+left_length,postorder-1,right_length);//遍历右子树
}
return 0;
}
int main()
{ //preorder为先序序列,inorder为中序序列,postorder为后序序列
int length;
char inorder_sequence[20],postorder_sequence[20],b;
//输入数据
scanf("%s%c",inorder_sequence,&b);
scanf("%s%c",postorder_sequence,&b);
length=strlen(inorder_sequence);
//前序遍历
preorder_traversal(inorder_sequence,postorder_sequence+length-1,length);
return 0;
}