二叉树
Posted shengzhe
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树相关的知识,希望对你有一定的参考价值。
树(树状图)
树状图是一种及其重要的数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。它具有以下的特点: 每个结点有零个或多个子结点;每一个非根结点只有一个父结点(度娘解释)
二叉树
特点
每个结点最多有两颗子树,结点的度最大为2(度即该节点孩子个数)
概念
- 满二叉树、完全二叉树
- 孩子、双亲、兄弟、祖先、子孙
- 深度
- 叶子结点、根节点
- 有序树、无序树
存储/建立方式
1.顺序存储
2.链表存储
(有时直接模拟链表)
typedef struct node* tree; struct node { int k; tree lchild,rchild; }t[1005];
遍历(以下例子为上图的前、中、后序遍历)
1.前序遍历(根-左-右
) FCADBEHGM
2.中序遍历(左-根-右
) ACBDFHEMG
3.后序遍历(左-右-根
) ABDCHMGEF
4.层次遍历
已知前序中序求后序
一个很经典的问题呢……我校OJ将近300个A掉的
例如下面这样一棵二叉树:
此树的前序遍历为:1 2 4 8 9 5 10 3 6 7
此树的中序遍历为:8 4 9 2 5 10 1 6 3 7
其实你在什么都不知道的时候拿到一个前序遍历和相对应的后序遍历是很难算出它的中序遍历的
但其实此题也有规律
因为前序遍历的顺序是“根-左-右
”
对应的 1 2 4 8 9 5 10 3 6 7肯定也是这个顺序
所以1为此树的根节点
反观中序遍历,顺序为“左-根-右
”
而根1在中序遍历的第7个
所以1左边的便是这棵二叉树的左子树,1右边的便是右子树
于是我们又能把它前序遍历分为三部分(根、左、右)了
对于它的左子树与他的右子树,我们能分别确定他们的前序遍历与中序遍历,再按照上述步骤反复即可
怎么样,看完是不是感觉其实就是个递归?
定义一个递归函数进行操作即可
于是写了下面的程序:
#include <iostream> #include <cstring> using namespace std; typedef struct node* tree; struct node { tree lc,rc; char k; }Tree[30]; int num=0; char str1[30],str2[30]; void print(node *t) { if(t->lc!=NULL) print(t->lc); if(t->rc!=NULL) print(t->rc); cout<<t->k; return; } tree build(int l1,int l2,int r1,int r2) { Tree[num].lc=Tree[num].rc=NULL; tree ret=&Tree[num++]; ret->k=str1[l1]; int root; for(int i=r1;i<=r2;i++) { if(str2[i]==str1[l1]) { root=i; break; } } if(root!=r1) ret->lc=build(l1+1,l1+(root-r1),r1,root-1); if(root!=r2) ret->rc=build(l1+(root-r1)+1,l2,root+1,r2); return ret; } int main() { cin>>str1>>str2; int len1=strlen(str1); int len2=strlen(str2); print(build(0,len1-1,0,len2-1)); return 0; }
但细想之后你会发现一件事:
你在建立二叉树的时候已经分别对“左”和“右”进行操作了(build函数13~16行),而“根”却没有被同时进行操作,岂不是很浪费?
题目最后求的是后序遍历,那只要在过程中按照左-右-根
的顺序在递归完左子树与右子树后输出根不就是后序遍历了吗
这样的话既不需要开结构体存储,也不需要进行最后打印:)
本蒟蒻的改善代码:
#include <iostream> #include <cstring> using namespace std; string p,m; int work(int l1,int r1,int l2,int r2) { int root=int(p[l1]-‘A‘); for(int i=l2;i<=r2;i++) { if(m[i]==p[l1]) { int c=i-l2; if(i>l2) work(l1+1,l1+c,l2,i-1); if(i<r2) work(l1+c+1,r1,i+1,r2); cout<<p[l1];//左-右-根 break; } } return root; } int main() { cin>>p>>m; work(0,p.size()-1,0,p.size()-1); return 0; }
现在知道中序遍历与后序遍历求前序遍历你也会了
但这里有一个问题
知道了一棵二叉树的前序遍历与后序遍历,却不能求出它的唯一中序遍历
这是因为能够造出两棵不同的二叉树,使得它们的前序遍历与后序遍历都一样
难道我们通过遍历构造二叉树仅仅局限于此了吗?当然不是
已知中序遍历与层次遍历推前序/后序
主要思路:可以直接进行前序遍历与后序遍历,每次传参数l1,r1,l2,r2
l1和r1是变化的,指目前这棵子树的中序遍历
l2和r2是不变的,指层次遍历(层次遍历无法直接分左子树与右子树)
每次在中序遍历中搜索当前节点(层次遍历)的值,因为层次遍历是从上向下的,所以不必担心顺序问题
如果可以搜到当前结点,证明此节点既在层次遍历里出现过,又在当前子树的中序遍历出现过
而它又是第一个搜索到的(上文提及层次遍历是顺序的),所以这个节点就是当前子树的根
伪代码(前序遍历)
void walkf(int l1,int r1,int l2,int r2) { 枚举层次遍历(l2->r2) { 枚举中序遍历(l1->r1) { 如果可以搜索到 { 存储节点并作标记; } } } cout<<当前子树的根; 遍历左子树; 遍历右子树; }
完整代码
#include<iostream> #include<algorithm> using namespace std; string mid,level; int root; int son[1000][2]={0}; //输出前序 void walkf(int l1,int r1,int l2,int r2) { int p; for(int i=l2;i<=r2;i++) { int c=0; for(int j=l1;j<=r1;j++) { if(mid[j]==level[i]) { p=j; c=1; break; } } if(c==1) break; } cout<<mid[p]; if(p>l1) walkf(l1,p-1,l2,r2); if(p<r1) walkf(p+1,r1,l2,r2); return; } //输出后序 void walkb(int l1,int r1,int l2,int r2) { int p; for(int i=l2;i<=r2;i++) { bool c=false; for(int j=l1;j<=r1;j++) { if(mid[j]==level[i]) { p=j; c=true; break; } } if(c) break; } if(p>l1) walkb(l1,p-1,l2,r2); if(p<r1) walkb(p+1,r1,l2,r2); cout<<mid[p]; return; } //还原树 int dfs(int l1,int r1,int l2,int r2) { int p; for(int i=l2;i<=r2;i++) { bool c=false; for(int j=l1;j<=r1;j++) { if(mid[j]==level[i]) { p=j; c=true; break; } } if(c) break; } if(p>l1) son[p][0]=dfs(l1,p-1,l2,r2); if(p<r1) son[p][1]=dfs(p+1,r1,l2,r2); return p; } int main() { cin>>level>>mid; walkf(0,mid.size()-1,0,level.size()-1); cout<<endl; walkb(0,mid.size()-1,0,level.size()-1); root=dfs(0,mid.size()-1,0,level.size()-1); return 0; }
以上是关于二叉树的主要内容,如果未能解决你的问题,请参考以下文章