数据结构(C语言)有关二叉树的相关算法题及思考
1.实验目的(结出本次实验所涉及并要求掌握的知识点)
1、掌握二叉树的动态存储结构及表示方法
2、掌握二叉树的前序、中序和后序遍历的递归和非递归算法、掌握二叉树的层次遍历算法。
3、运用二叉树的3种遍历算法求解基于二叉树的有关问题。
2.实验内容(结出实验内容具体描述)
①编写算法函数void preorder1(bintree t)实现二叉树t的非递归前序遍历。
②编写算法函数void levelbintree(bintree t),实现二叉树的层次遍历。
③编写函数bintree prelist(bintree t),bintree postfirst(bintree t),分别返回二叉树t在前序遍历下的最后一个结点地址和后序遍历下的第一个结点地址。
④假设二叉树采用链式方式存储,t为其根结点,编写一个函数int Depth(bintree t, char x),求值为x的结点在二叉树中的层次。
⑤试编写一个函数,将一棵给定二叉树中所有结点的左、右子女互换。
⑥试编写一个递归函数bintree buildBintree(char *pre, char *mid, int length),根据二叉树的前序序列pre、中序序列mid和前序序列长度length,构造二叉树的二叉链表存储结构,函数返回二叉树的树根地址。
3.算法描述及实验步骤(用适当的形式表达算法设计思想与算法实现步骤)
①编写算法函数void preorder1(bintree t)实现二叉树t的非递归前序遍历。
利用栈的特点,首先将根节点输出后,把根节点入栈,再让t指向其左儿子,一直通过while循环去判断t是否为空,否则一直沿着左儿子的路径往下走。如果遇见叶子节点则先打印叶子节点再让叶子结点入栈,因为叶子节点找不到左儿子则将叶子结点出栈后去遍历其父节点的右儿子,如果右儿子有左儿子则反复上述操作,如果无则同样返回父节点去寻找右儿子。
#include "bintree.h"
#include "seqstack.h"
char *a="ABC##D#E##F##";
void preorder(bintree t)
{
seqstack s;
s.top=0;
while(t||s.top!=0)
{
if(t)
{
printf("%c",t->data);
push(&s,t);
t=t->lchild;
}
else
{
t = pop(&s);
t=t->rchild;
}
}
}
int main()
{
bintree t;
t=createbintree();
printf("二叉树的前序序列为:\n");
preorder(t);
return 0;
}
②编写算法函数void levelbintree(bintree t),实现二叉树的层次遍历。
利用队列的特点,将入队列时的结点的儿子节点全部放进队列的后方等待前面被提取。前面还是按照队列的顺序每次循环依次打印队列的值,然后再往后移动一位。这样就可以按照层数的顺序依次打印了。
#include "bintree.h"
char *a="ABC##D#E##F##";
void levelbintree(bintree t)
{
bintree queue[100];
int f,r;
bintree p;
f=0;r=1;queue[0]=t;
while(f<r)
{
p=queue[f];f++;printf("%c",p->data);
if(p->lchild)
{
queue[r]=p->lchild;
r++;
}
if(p->rchild)
{
queue[r]=p->rchild;
r++;
}
}
}
int main()
{
bintree t;
t=createbintree();
printf("二叉树的层次序列为:\n");
levelbintree(t);
return 0;
}
③编写函数bintree prelist(bintree t),bintree postfirst(bintree t),分别返回二叉树t在前序遍历下的最后一个结点地址和后序遍历下的第一个结点地址。
前序遍历如果要找最后一个结点,在p不为空且p有左子节点或者右子节点的情况下,在右子节点不为空的情况下先去找右子节点,右子节点为空则返回左子节点。最后找到的结点无左右子树则跳出循环,并返回p的结点地址为前序遍历下最后一个结点地址。
同理后序遍历下的第一个结点的地址则去不停的往下寻找左节点,知道节点没有左子树再去看是否有右子树,如果没有则返回节点地址,如果有则继续进入右子树去寻找左下角的结点。
#include "bintree.h"
char *a="ABC##D##EF#G###";
bintree prelast(bintree t)
{
bintree p=t;
if(p)
{
while(p&&p->lchild||p->rchild)
{
if(p->rchild)
p=p->rchild;
else p=p->lchild;
}
}
return p;
}
bintree postfirst(bintree t)
{
bintree p;
p=t;
if(t)
{
while(p&&p->lchild||p->rchild)
{
if(p->lchild)
{
p=p->lchild;
}
else
{
p=p->rchild;
}
}
}
return p;
}
int main()
{
bintree t,p,q;
t=createbintree();
p=prelast(t);
q=postfirst(t);
if(t!=NULL)
{
printf("前序遍历最后一个结点为:%c\n",p->data);
printf("后序遍历第一个结点为:%c\n",q->data);
}
else printf("二叉树为空");
return 0;
}
④假设二叉树采用链式方式存储,t为其根结点,编写一个函数int Depth(bintree t, char x),求值为x的结点在二叉树中的层次。
利用递归的思想。首先考虑怎么走,这个x可能再根节点的左子树中,也可能在右子树中,所以我们需要两个depth函数递归去分别找寻根节点左边和右边的值为x的结点。如果一直往下找都没找到则返回的是0,否则如果找到了则返回1.然后知道了上一层循环的其中一个Num是1则清楚值为x的结点在这个子树中,就开始计算层数了,逐层往上的时候n都加一,并返回n值。到最后n就是层数了。
#include "bintree.h"
#include "seqstack.h"
char *a="ABC##D##EF#G###";
int depth(bintree t,char x)
{
int num1,num2,n;//num1,num2分别记录在左子树,右子树中查找到x的层数,n记录最终返回的结果层数
if(t==NULL)
{
return 0;
}
else
{
if(t->data==x)
{
return 1;
}
num1=depth(t->lchild,x);
num2=depth(t->rchild,x);
n=num1+num2; //num1和num2之中必有一个为0
if(num1!=0||num2!=0) //找到了x ,往回数
{
n++;
}
}
return n;
}
int main()
{
bintree root;
char x;
int k=0;
root=createbintree();
printf("请输入树中1个结点的值:\n");
scanf("%c",&x);
k=depth(root,x);
printf("%c结点的层次为%d\n",x,k);
return 0;
}
⑤试编写一个函数,将一棵给定二叉树中所有结点的左、右子女互换。
利用递归的思想。 通过bintree p先转换根节点左右子结点,然后再将通过change(t->lchild)change(t->rchild)同理去转换左右子节点的左右子树,知道t为空返回null。
#include "bintree.h"
#include "seqstack.h"
char *a="ABC##D##EF#G###";
void preorder(bintree t)
{
seqstack s;
s.top=0;
while(t||s.top!=0)
{
if(t)
{
printf("%c",t->data);
push(&s,t);
t=t->lchild;
}
else
{
t = pop(&s);
t=t->rchild;
}
}
}
void change(bintree t)
{
bintree p;
if(t==NULL) ;
else
{
p=t->lchild;
t->lchild=t->rchild;
t->rchild=p;
change(t->lchild);
change(t->rchild);
}
}
int main()
{
bintree root;
root=createbintree();
change(root);
preorder(root);
return 0;
}
⑥试编写一个递归函数bintree buildBintree(char *pre, char *mid, int length),根据二叉树的前序序列pre、中序序列mid和前序序列长度length,构造二叉树的二叉链表存储结构,函数返回二叉树的树根地址。
利用递归的思想。先创建一个内存去存放函数前序遍历的字符串的第一个结点,因为这个结点肯定是根节点。然后根据此时前序遍历的长度还有没有子串以及中序遍历是否能找到根节点去i++找到根节点在中序遍历中的值,就能找到左子树有i个子孙结点,右子树有length-i+1个子孙节点。然后运用递归的思想将t->lchild再去buildbintree(pre+1,mid,i) 同理t->rchild=buildBintree(pre+i-1,mid+i-1,length-i-1);这里第一个参数表示的是左子树中去找到左子树关于前序遍历的根节点,第二个参数中序如果是左子树则不变,如果是右子树则将中序遍历通过mid+i+1去找到根节点右边的子串,第三个参数长度为左右子树里子孙结点的长度。最后将t返回就存储好了一个二叉树的结构。可以用后序去遍历了。
#include "bintree.h"
#include <string.h>
char *a="";
void postorder(bintree t)
{
if(t)
{
postorder(t->lchild);
postorder(t->rchild);
printf("%c",t->data);
}
}
bintree buildBintree(char *pre, char *mid,int length)
{
bintree t;
int i=0;
if(length)
{
t=(bintree)malloc(sizeof(binnode)); //生成新结点
t->data=pre[i];
while(i<length&&mid[i]!=pre[0]) //在中序遍历中查找根结点的位置
i++;
t->lchild=buildBintree(pre+1,mid,i);
t->rchild=buildBintree(pre+i+1,mid+i+1,length-i-1);
}
else
return NULL;
return t;
}
int main()
{
bintree root;
char pre[100],mid[100];
puts("请输入前序序列:");
gets(pre);
puts("请输入中序序列:");
gets(mid);
root=buildBintree(pre,mid,strlen(pre));
puts("后序序列是:");
postorder(root);
return 0;
}
4.调试过程及运行结果(详细记录在调试过程中出现的问题及解决方法。记录实验执行的结果)
①编写算法函数void preorder1(bintree t)实现二叉树t的非递归前序遍历。
一开始可能是无法通过栈的思想去解决这道问题,上课老师讲解了栈的思想后明白这道题的思路。
②编写算法函数void levelbintree(bintree t),实现二叉树的层次遍历。
利用了树中层次遍历的思想去利用队列的思想解决了这道题。
③编写函数bintree prelist(bintree t),bintree postfirst(bintree t),分别返回二叉树t在前序遍历下的最后一个结点地址和后序遍历下的第一个结点地址。
这道题一开始没有思考到前序遍历下的最后一个结点是右下角的哪个结点,怎么用非递归的思想去解决。如果这个结点有右子叶子节点则这个叶子结点是最后一个,如果没有右叶子结点有左叶子结点,则这个左叶子结点是最后一个。
④假设二叉树采用链式方式存储,t为其根结点,编写一个函数int Depth(bintree t, char x),求值为x的结点在二叉树中的层次。
一开始一直觉得层数一定要通过递归从上往下计算,但是没想到可以先找到值为x的结点通过往回返的过程n++;最后得到的n就是层数。
⑤试编写一个函数,将一棵给定二叉树中所有结点的左、右子女互换。
左后子女互换这里首先想到了递归。然后一开始以为是左右子节点的值互换,但是后来发现这样不行,必须左右结点的bintree互换。这样他的子树的子孙树才会一起跟着换。
⑥试编写一个递归函数bintree buildBintree(char *pre, char *mid, int length),根据二叉树的前序序列pre、中序序列mid和前序序列长度length,构造二叉树的二叉链表存储结构,函数返回二叉树的树根地址。
这道题其实不怎么会,所以去网上查找了一下。发现了递归思想的解答。首先通过前序第一个去寻找根节点并根据中序查找根节点去判断左右子串的长度,然后再通过递归去将t->lchild赋值buildbintree(pre+1,mid,i);t->rchild赋值buildbintree(pre+i+1,mid+i+1,length-i-1);
5.总结(对实验结果进行分析,问题回答,实验心得体会及改进意见)
通过这次的二叉树的运用,深刻懂得了很多递归的思想与非递归的思想。递归中可以通过从后往前的思想去计算递归的次数。可以通过利用中介bintree p去实现左右子树的互换。通过前序和中序去创建一个二叉树结构……这些思想都可以运用到以后的实际案例中。收获很大。
6、一些头文件
bintree.h
#include <stdio.h>
#include <stdlib.h>
#define N 100
extern char *a;
typedef struct node
{
char data;
struct node *lchild,*rchild;
}binnode;
typedef binnode *bintree;
/*前序序列创建二叉树*/
bintree createbintree()
{
char ch=*a++;
bintree t;
if(ch=='#') t=NULL;
else
{
t=(bintree)malloc(sizeof(binnode));
t->data=ch;
t->lchild=createbintree();
t->rchild=createbintree();
}
return t;
}
seqstack.h
typedef struct lstack
{
bintree data[100];
int tag[100];
int top;
}seqstack;
void push(seqstack *s,bintree t)
{
s->data[s->top]=t;
s->top++;
}
bintree pop(seqstack *s)
{
if(s->top!=0)
{
s->top--;
return (s->data[s->top]);
}
else return NULL;
}