这些题都是对二叉树的一些操作,现在我根据我对二叉树的理解来给大家讲讲吧。
我这里主要讲二叉树的各种操作,至于二叉树的定义请看这篇文章。
好,现在咱们来讲二叉树的创建。
我们要知道二叉树有父结点,左孩子结点,右孩子结点,像这样
A是B,C的父节点,B是A的左孩子结点,C是A的右孩子结点。
我们要创建二叉树就采用递归的方式创建。
先创建一个结构体
typedef struct tree{
int data;//data是数据域,数据类型依题而论。
tree *Lchild,*rchild;//创建左右孩子结构体指针。
} tree,*butree;
创建好结构体后,开始递归创建二叉树,这里我们根据西科大OJ的题目,采用先序创建二叉树
tree creat(){
char a;
cin >> a;
node *p;
p=NULL;
if(a=='#'){
return NULL;
}
else {
p=(node*)malloc(sizeof(node));
p->data=a;
p->Lchild=creat();
p->rchild=creat();
return p;
}
}
直接看代码我相信你们肯定会有不懂,我画图来说明。
我们先将先序表达式依次输入,比如现在我们要输入ABC####
我们将要构建出这样的二叉树:
我们要实现这个目标就采用递归的的方式,依次读取输入的符号。
先读入一个符号A,这时创建结点A,
随后进入递归,让A结点的左孩子结点为递归返回的结果。
输入符号B,创建结点,再次进入递归,找B的左孩子结点。
输入符号C,创建结点,再次进入递归,找C的左孩子结点。
这时我们读入‘#’,所以返回一个NULL值,回到上一层递归,得到C的左结点为‘#’即为NULL。
这时进入C的右孩子结点递归,我们读入‘#’,告诉我们该结点为NULL,即C的右结点为NULL,
随后返回C,这里相当于是让B的左节点成为C。
然后进入B的右孩子结点递归,我们再次读入‘#’,告诉我们B的右孩子结点为空。
随后返回上一层递归,即A的左结点为B,这时进入A的右孩子结点递归,
我们再次读入‘#’,即A的右孩子结点为NULL,最后将A结点返回,递归结束,成功创建二叉树。
这里我们没有用void定义这个creat函数,是因为我们返回的值为结构体指针,所以用tree来定义这个函数。
至此,我们完成了创建二叉树,接下来就是对二叉树进行遍历。
遍历的方式有三种
- 先序遍历。
- 中序遍历。
- 后续遍历。
现在我们来具体看看这三个遍历分别是指什么。
比如咱们对于这样一个二叉树
如果我们采用先序遍历,输出结果为A,B,C。
即输出顺序为根结点,左孩子结点,右孩子结点。
如果我们采用中序遍历,即为B,A,C;
输出顺序为左孩子结点,跟结点,右孩子结点
如果我们采用后序遍历,那么序列为B,C,A
即先左孩子结点,再右孩子结点,最后根结点
我们要做到这种的话我们可以采用递归方式和非递归方式。这里我只讲递归方式。
void xian(node *p){//先序遍历
if(p!=NULL){
cout << p->data << " ";
xian(p->Lchild);
xian(p->rchild);
}
}
void hou(node *p){//后序遍历
if(p!=NULL){
hou(p->rchild);
hou(p->Lchild);
cout << p->data << " ";
}
}
void zhong(node *p){//中序遍历
if(p!=NULL){
zhong(p->Lchild);
cout << p->data << " ";
zhong(p->rchild);
}
}
求先序时,我们从根结点出发,先输出该结点的数据,然后进入左孩子结点递归,进入左孩子结点后,我们将左孩子结点看作是根结点,然后输出该结点数据,再递归进入该节点的左孩子结点,以此类推,直到找到一个结点为NULL,退回上一层递归,然后找右孩子结点,一层一层回溯,完成先序输出。
求中序也是类似,不过就是将输出的位置换一下,即不停的找左结点,直到不存在左孩子结点后,开始回溯,每回溯以此输出以此该结点的值,并且查看是否在该结点存在右孩子结点,如果有就再次进入递归,以此类推,即完成中序输出。
求后序也是,先左结点递归,完了后再进行右结点递归,随后依次回溯进行输出,完成后序输出。
咱们完成遍历后还有几个操作,分别是
- 求二叉树深度。
- 求二叉树宽度。
- 求二叉树叶子结点。
- 求二叉树度为x的结点。
我们依次来看吧,首先,我们看求二叉树深度。
在求二叉树深度时,我们先说一下二叉树深度是什么。
二叉树深度就是二叉树元素最多的那一支的长度。
比如:
元素最多的那一只为A,B,D,F,长度为4,即二叉树深度为4.
了解了这个概念后,我们就可以开始实现了,对于求深度的方法任仍然有两种方式,一种为递归,第二种为非递归,采用队列来求,我这里就只讲递归的方式了,这样方便大家理解。
int deep(node *p){//求二叉树深度
if(p==NULL){
return 0;
}
else{
int L=deep(p->Lchild);
int R=deep(p->rchild);
int maxx=max(L,R);
return maxx+1;
}
}
这里我们要定义全局变量maxx来存储深度值。
开始,我们判断该结点是否为NULL,如果不是,那么就进入递归,我们定义一个L表示左孩子结点长度,随后进入左孩子结点,继续操作。
如果遇到结点为NULL,我们返回0,回到上一层函数,上一层L就为0,随后我们看R,也是进行递归,直到结点NULL,然后开始回溯,每次都对L和R进行比较,取最大值,每层都将maxx+1.
我们这相当于是从下往上数,而不是从上往下数,最终得到一个最大的maxx,即为二叉树深度。
各位如果还是不理解,我这里建议大家画图来理解。
好,我们完成了求二叉树深度后,开始求二叉树宽度。
老样子,我们得知道二叉树宽度是什么。
通俗点说,二叉树宽度就是二叉树有多宽。即二叉树每层存在结点的数量最大值。
如图所示,这样想必大家一定就比较清楚了。
好,概念知道了,咱们就来把他实现。
int maxx=0;
int width_[1000000];
int width(node *p,int k){//求二叉树宽度
if(p==NULL){
return 0;
}
else{
width_[k]++;
maxx=max(maxx,width_[k]);
width(p->Lchild,k+1);
width(p->rchild,k+1);
return maxx;
}
}
我们利用数组下标来代表二叉树层数,数组里的数据的多少代表该层有多少个结点。
老样子,我们仍然是递归。
如果该结点不为NULL,那么我们先给该层结点数量加一,用maxx实时获取最大宽度。
然后进入左节点递归,并且将层数加一,即k+1;
直到出现P为NULL,然后开始回溯,找右孩子结点,如此层次递归回溯,即可得到最大值,即二叉树宽度。
画图看比较清楚。
了解完求二叉树宽度后,那我们来做最简单的找叶子结点数量和度为x的结点数量。
那这叶子结点和度是什么呢?
我们要懂得联想,既然它叫叶子结点,那么我们能想到什么?
没错,叶子只与树枝相连,然后就没有其他东西与它相连了。
放到二叉树里来说,就是只有根结点,而没有左孩子结点和右孩子结点的结点。
好,我们在说度,通俗点讲,这度就相当于是连接的结点数量,如果为1,则代表该结点只连了一个子节点,也就是说要么存在左孩子结点,要么存在右孩子结点。
度为2就不说了,两个都有嘛。
好,知道这些,我觉得大家应该脑海中就有思路了吧。
int yezi(node *p){//求二叉树叶子结点个数
if(p==NULL){
return 0;
}
else if(p->Lchild==NULL&&p->rchild==NULL){
return yezi(p->Lchild)+yezi(p->rchild)+1;
}
else{
return yezi(p->Lchild)+yezi(p->rchild);
}
}
int yezi2(node *p){//求度为2的结点个数
if(p==NULL){
return 0;
}
else if(p->Lchild!=NULL&&p->rchild!=NULL){
return yezi2(p->Lchild)+yezi2(p->rchild)+1;
}
else{
return yezi2(p->Lchild)+yezi2(p->rchild);
}
}
简简单单采用递归的方式解决,这么简单的递归我相信大家都理解,我就不再多做解释了。
好啦,如果你把这篇文章读完,相信你能够轻松解决OJ上的题了,如果这样你都解决不了,那你可以私聊我,感谢各位阅读