【算法总结】二叉树
我们从二叉树的遍历谈起。
众所周知,在对二叉树的遍历过程中,根据遍历每一个结点的左子树、结点本身、右子树的顺序不同可将对二叉树的遍历方法分为前序遍历、中序遍历、后序遍历。我们摒弃数据结构教科书上复杂的遍历方式,而是使用我们在上一章所重点讨论过的递归程序来简单的实现它。
假设二叉树结点由以下结构体表示:
struct Node { Node *lchild;//指向其左儿子结点的指针,当其不存在左儿子时为NULL Node *rchild;//指向其右儿子结点的指针,当其不存在右儿子时为NULL /* * * 其他节点信息的操作*/ } ;
我们以中序遍历为例,给出其遍历方法。
void inOrder(Node *Tree) { if(Tree->lchild != NULL)inOrder(Tree->lchild);//递归遍历左子树 /* * * 对当前根结点做遍历操作*/ if(Tree->rchild != NULL)inOrder(Tree->rchild);//递归遍历右子树 return; }
如上所见,用递归方式编写的二叉树遍历代码较原始使用堆栈来编写的相同功能代码,在代码量上得到巨大的优化。为了完成对二叉树的中序遍历,在遍历任意一个结点 Tree 时,我们首先递归遍历其左儿子及其子树,再遍历该结点本身,最后遍历其右儿子及其子树,从而完成对二叉树的中序遍历。 相同的,若我们需要其他两种形式的遍历方式,只需简单的修改遍历自身结点和递归遍历左右儿子的相关语句顺序即可。
解题思路:
该例题涉及二叉树的建立、由二叉树的两种遍历结果还原二叉树、二叉树的遍历等多种知识点。我们以分析该例题为例,介绍关于二叉树各知识点。
由该例要求,首先我们需要根据给定的二叉树前序和中序遍历结果还原该二叉树。其次,我们需要将还原的二叉树以二叉树的形式保存在内存中。最后,我们需要对建立的二叉树进行后序遍历。
后序遍历在前文中已经有所提及。下面我们对前两部分做重点的阐述。
由给定的前序和中序遍历还原得到该二叉树。以前序遍历结果 XDAGFE, 和中序遍历结果 ADGXFE 为例详细讨论其还原方法。
由前序遍历结果的首个元素为 X 可知,原树必是由 X 为根结点。在中序遍历中,遍历结果 ADGXFE 以 X 为界分为两个子串。其中第一个子串 ADG 为 X 的左子树的中序遍历结果,第二个子串 FE 为 X 的右子树的中序遍历结果。这样我们知道 X 的左子树具有 3 个元素,X 的右子树具有 2 个元素。根据元素的数量我们同样可以得知,在先序遍历中去除根结点 X 后剩余的串 DAGFE 中,前 3 个字符 DAG 为 X 的左子树的前序遍历结果,后 2 个字符 FE 为 X 的右子树的前序遍历结果。
同样的对于确定的左子树前序遍历结果 DAG 和中序遍历结果 ADG 重复以上确定过程,可知 D 为该子树根结点,其左儿子为 A,右儿子为 G。
X 的右子树前序遍历结果 FE 和中序遍历结果 FE 同样可以确定该子树以 F 为根节点,其左儿子不存在,右儿子为 E。
这样我们就还原了原始二叉树。
我们还需将还原出来的树保存在内存中。使用结构体:
struct Node//树结点结构体 { Node *lchild;//左儿子指针 Node *rchild;//右儿子指针 char c;//结点字符信息 }Tree[50];//静态内存分配数组
表示树的一个结点,其字符信息保存在字符变量 c,若该结点存在左儿子或者右儿子,则指向他们的指针保存在 lchild 或 rchild 中,否则该指针为空。
下面给出本例代码,详细了解其实现。
#include<cstdio> #include<cstring> struct Node//树结点结构体 { Node *lchild;//左儿子指针 Node *rchild;//右儿子指针 char c;//结点字符信息 }Tree[50];//静态内存分配数组 int loc;//静态数组中已经分配的结点个数,方便确定下标 Node *creat() //申请一个结点空间,返回指向其的指针 { Tree[loc].lchild = Tree[loc].rchild = NULL;//初始化左右儿子为空 return &Tree[loc++];//返回指针,且loc累加 } char str1[30], str2[30];//保存前序和中序遍历结果的字符串 void postOrder(Node *T)//后序遍历 { if (T->lchild != NULL)postOrder(T->lchild);//左子树不为空,递归遍历左子树 if (T->rchild != NULL)postOrder(T->rchild);//右子树不为空,递归遍历右子树 printf("%c", T->c);//遍历该结点,输出其字符信息 } Node *build(int s1, int e1, int s2, int e2)//由字符串的前序遍历和中序遍历还原原树,并返回其根节点,前序遍历结果由str[s1]到str1[e1],后序遍历结果为str2[s2]到str2[e2] { Node *ret = creat();//为该树根结点申请空间 ret->c = str1[s1];//该结点字符为前序遍历中的第一个字符,即根结点 int rootidx;//前序根结点在中序中的位置编号 for (int i = s2; i <= e2; i++)//查找该根结点字符在中序遍历中的位置,这样可以分成两个子树 { if (str2[i] == str1[s1]) { rootidx = i; break; } } if (rootidx != s2)//如果左子树不为空 { ret->lchild = build(s1 + 1, s1 + (rootidx - s2), s2, rootidx - 1);//递归还原左子树 } if (rootidx != e2)//如果右子树不为空 { ret->rchild = build(s1 + (rootidx - s2) + 1, e1, rootidx + 1, e2);//递归还原右子树 } return ret;//返回根结点指针,此时根结点是还原二叉树的根结点 } int main() { while (scanf("%s", str1) != EOF) { scanf("%s", str2); loc = 0;//初始化静态内存空间中已经使用结点的个数为0 int L1 = strlen(str1); int L2 = strlen(str2);//计算两个字符串的长度 Node *T = build(0, L1 - 1, 0, L2 - 1);//还原整棵树,其根结点保存在T中 postOrder(T);//后序遍历 printf(" ");//输出换行 } return 0; }
#include<iostream> #include<cstring> using namespace std; int loc = 0; char s1[30]; char s2[30]; struct Node { Node* l; Node* r; char c; }t[30]; Node* create() { t[loc].l=t[loc].r=NULL; return &t[loc++]; } Node* build(int b1, int e1, int b2, int e2) { int idx = b2; Node* rt = create(); rt->c=s1[b1]; while(s2[idx]!=s1[b1])idx++; if(idx!=b2)rt->l=build(b1+1,b1+idx-b2,b2,idx-1); if(idx!=e2)rt->r=build(b1+idx-b2+1,e1,idx+1,e2); return rt; } void postorder(Node* t) { if(t->l!=NULL)postorder(t->l); if(t->r!=NULL)postorder(t->r); cout<<t->c; } int main() { while(cin>>s1>>s2) { loc = 0;//loc清零 int l1 = strlen(s1); int l2 = strlen(s2); Node* root = build(0,l1-1,0,l2-1); postorder(root); cout<<endl; } return 0; }
2016:重建二叉树
解题思路
详细讲解见二叉树专题。核心思想其实很简单,用数组就可以直接做,无非就是根据前序遍历的结果对中序遍历左右子串分别递归,最后后序打印根节点的值。但是实战不建议用这种方法,不好想,不好应对变式情况。
AC代码
#include<cstdio>
#include<cstring>
char s1[10000];//存储前序
char s2[10000];//存储中序
void f(int l, int l2, int r2)//总是需要前序遍历的起点和中序遍历的两端,以确定根结点和左右子树
{
if (l2 == r2)return;//没有元素了
char r = s1[l];//根结点
int loc = l2;
while (s2[loc] != r)loc++;//找到根结点在中序遍历中的位置
f(l + 1, l2, loc);
f(l + loc + 1 - l2, loc + 1, r2);//画一画情况就知道了
printf("%c", r);
}
int main()
{
while (scanf("%s%s", s1, s2) != EOF)
{
int len = strlen(s1);
f(0, 0, len);
printf("
");
}
return 0;
}
AC代码(常规做法)
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int loc;//重建树的结点下标 char s1[10000], s2[10000];//存储前序遍历和中序遍历结果 struct Node { Node *l; Node *r; char c; }t[10000]; Node* create()//新建一个树结点 { t[loc].l = t[loc].r = NULL;return &t[loc++]; } Node* build(int b1, int e1, int b2, int e2)//前序遍历和中序遍历的结果下标 { Node* root = create();//还原树的根结点 root->c = s1[b1]; int idx = 0;//根结点在中序遍历结果中的下标 while (s1[b1] != s2[idx])idx++; if (idx != b2) root->l = build(b1 + 1, e1 + idx - b2, b2, idx - 1);//递归还原左子树 if (idx != e2) root->r = build(b1 + 1 + idx - b2, e1, idx + 1, e2);//递归还原右子树 return root; } void postOrder(Node *t) { if (t->l != NULL)postOrder(t->l); if (t->r != NULL)postOrder(t->r); printf("%c", t->c); } int main() { while (scanf("%s%s", s1, s2) != EOF) { loc = 0;//初始化下标 int l1 = strlen(s1); int l2 = strlen(s2); Node* root = build(0, l1 - 1, 0, l2 - 1);//得到还原树根节点 postOrder(root); printf(" "); } //system("pause"); return 0; }
#include<cstdio> #include<cstring> #include<iostream> using namespace std; char s1[10000], s2[10000]; int loc, n; struct Node { Node* l; Node* r; char c; }t[10000]; Node* create() { t[loc].l = t[loc].r = NULL; return &t[loc++]; } Node* build(int b1, int e1, int b2, int e2) { Node* root = create(); root->c = s1[b1]; int idx = 0; while (s1[b1] != s2[idx])idx++; if (idx != b2)root->l = build(b1 + 1, e1 + idx - b2, b2, idx - 1); if (idx != e2)root->r = build(b1 + 1 + idx - b2, e1, idx + 1, e2); return root; } void postOrder(Node* t) { if (t->l != NULL)postOrder(t->l); if (t->r != NULL)postOrder(t->r); printf("%c", t->c); } int main() { while (scanf("%s%s", s1, s2) != EOF) { loc = 0; int l1 = strlen(s1); int l2 = strlen(s2); Node* root = build(0, l1 - 1, 0, l2 - 1); postOrder(root); printf(" "); } return 0; }