树结构的特点:
1他是一种层次关系的结构,每一层的元素可能与下一层的多个数据元素(孩子)相关,但只和上一层中的一个数据元素相关;
2每一个元素(除了整棵树的根节点之外)都可以递归地看做根节点,以及上一层节点的左孩子或者右孩子。树的遍历依据的就是这一思路;
二叉树的创建与操作:
定义二叉树的节点:
typedef struct node
{
char data; //二叉树的数据,可以是任何类型
struct node *lchild,*rchild; //指向该节点左孩子与右孩子的指针
}tree;
二叉树的遍历:
思想:递归地访问每一层的根节点,这就遍历了树的各个节点;
1先序遍历:
方法:1访问该层的根节点;
2递归地访问该层的左孩子;
3递归地访问该层的右孩子;
PreOrderTraverse(tree *T)
{
if(T) //递归的结束条件:孩子节点为空,即T为空指针
{
visit(T->data); //递归地访问每一层的根节点,visit函数体里写的是对每个节点要做的事
PreOrderTraverse(T->lchild );//先序遍历T的左子树
PreOrderTraverse(T->rchild);//先序遍历T的右子树
}
}
2中序遍历
方法: 1递归地访问该层的左孩子;
2访问该层的根节点;
3递归地访问该层的右孩子;
InOrderTraverse(tree *T)
{
if(T) //递归的结束条件:孩子节点为空,即T为空指针
{
InOrderTraverse(T->lchild );//中序遍历T的左子树
visit(T->data); //递归地访问每一层的根节点,visit函数体里写的是对每个节点要做的事
InOrderTraverse(T->rchild);//中序遍历T的右子树
}
}
3后序遍历
方法: 1递归地访问该层的左孩子;
2递归地访问该层的右孩子;
3访问该层的根节点;
PosOrderTraverse(tree *T)
{
if(T) //递归的结束条件:孩子节点为空,即T为空指针
{
PosOrderTraverse(T->lchild );//中序遍历T的左子树
visit(T->data); //递归地访问每一层的根节点,visit函数体里写的是对每个节点要做的事
PosOrderTraverse(T->rchild);//中序遍历T的右子树
}
}
树的创建:
二叉树的创建是通过树的遍历实现的:
遍历着创建节点,如果节点不是终结,则对节点的数据成员赋值;否则对指针赋予NULL;
因为后者要在函数体内对指针赋值(赋空指针),所以需要把指针(树节点类型)的指针作为参数传入函数体内:
void CreateTree(tree**T) //因为要在函数体里给指针赋值,所以传递的是指针的指针
{
}
向节点内输入数据:
void CreateTree(tree**T) //因为要在函数体里给指针赋值,所以传递的是指针的指针
{
char c;
scanf("%c",&c);
}
然后判断,如果输入的是空字符,就对所访问的树节点的指针赋予空指针,函数返回;
void CreateTree(tree**T) //因为要在函数体里给指针赋值,所以传递的是指针的指针
{
char c;
scanf("%c",&c); //给结点数据赋值
if(c==' ')
{(*T)=NULL;} //输入空字符则为空指针
}
如果输入的不是空字符,则将其输入当前节点,并递归地访问左子树和右子树:
(这里if_else语句结构还起到递归结束条件的作用——不符合条件不继续深入递归,递归返回)
void CreateTree(tree**T) //因为要在函数体里给指针赋值,所以传递的是指针的指针
{
char c;
scanf("%c",&c); //给结点数据赋值
if(c==' ')
{(*T)=NULL;} //输入空字符则为空指针
else
{/*以下两句相当于visit函数*/
(*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
}
并递归地访问左子树和右子树:
void CreateTree(tree**T) //因为要在函数体里给指针赋值,所以传递的是指针的指针
{
char c;
scanf("%c",&c); //给结点数据赋值
if(c==' ')
{(*T)=NULL;} //输入空字符则为空指针
else
{/*以下两句相当于visit函数*/
(*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
/*以下两句和先序遍历的一般形式稍有不同*/
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
/*递归地创建孩子结点。注意,由于要在函数体里给指针赋值,所以传递的是指针的指针*/
}
}
例题:用先序遍历创建如下的树:
A-B,A-E;B-C,B-D;E-F
并输出D位于二叉树的层数;
首先,创建一个二叉树(前面有,照抄就行,这里就不多说了)。
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void visit(char c,int index);
void PreOrderTraverse(tree *T,int index);
main()
{
tree *T;
CreateTree(&T);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
然后设计visit函数:思路是遍历,找到字符“D”,然后输出对应的层数。这里采用前序遍历:
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void PreOrderTraverse(tree *T);
main()
{
tree *T;
CreateTree(&T);
PreOrderTraverse(T,1);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
void PreOrderTraverse(tree *T)
{ if(T)
{
visit(T,);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
为了得到当前二叉树的层数,需要对先序遍历的算法加以改造以记录递归地层数。这里采用“递归计数器”的方式,给递归函数的参数增加一个,而在递归每深入一层时,该参数都递增1(注意是表达式加1,不得用递增运算符,这样会比实际层数多1或少1,因为递增运算符加1操作和所在表达式的运算不是同时的),同时注意参数的传递必须仅仅是单值传递,而不是指针的传递,每一层中参数的局部值都不会受到下一层任何操作的影响。
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void PreOrderTraverse(tree *T,int level);
main()
{
tree *T;
CreateTree(&T);
PreOrderTraverse(T,1);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
void visit(tree *T,int flag,int level,int n)
{
}
void PreOrderTraverse(tree *T,int level)
{ if(T)
{
visit()
PreOrderTraverse(T->lchild,level+1);
PreOrderTraverse(T->rchild,level+1);
}
}
现在开始设计visit函数:每到达一个节点,就检查该节点是不是我们要找的(“D”),并输出所在的层数,所以要传入节点的字符(T->data)以及对应的层数(level)两个参数;并在函数体内执行前面所述的算法:
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void visit(char c,int level);
void PreOrderTraverse(tree *T,int index);
main()
{
tree *T;
CreateTree(&T);
PreOrderTraverse(T,1);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
void visit(char c,int level)
{
if(c==’D’)
printf(“%c is at the %d level of the tree/n”,c,level);
}
void PreOrderTraverse(tree *T,int level)
{ if(T)
{
visit(T->data,level);
PreOrderTraverse(T->lchild,level+1);
PreOrderTraverse(T->rchild,level+1);
}
}
下面这一题要用到前面这一题的一些东西:
判断完全二叉树
分析:满足什么条件的才是完全二叉树?由题目叙述可以知道:如果树的深度为k
1则第1到第k-1层必须构成一个满二叉树,即第1到第k-2层每个结点既有左孩子又有右孩子;
2在第k-1层中,一旦有一个节点的右孩子为空,则同层中后继结点的左右孩子都为空,一旦有一个节点左孩子为空,该节点的右孩子以及同层中后继结点的左右孩子都为空;
遍历所有节点,所有节点两个条件都满足,则为完全二叉树;
有一个节点不满足就不是,同时退出遍历;
据此,可以得到算法的伪代码:
Boolean isJust()
{
遍历所有节点
{
If(不满足完全二叉树条件)
return false;
}
return true;
}
首先,可以直接将上一题创建树的代码搬过来:
遍历树:
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void visit(char c,int level);
void PreOrderTraverse(tree *T,int index);
main()
{
tree *T;
CreateTree(&T);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
把分析的伪代码接上去,构造遍历和判断完全二叉树的代码:
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T);
void visit(char c,int level);
void PreOrderTraverse(tree *T,int index);
main()
{
tree *T;
CreateTree(&T);
PreOrderTraverse(T,1);
}
void CreateTree(tree**T)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild));
CreateTree(&((*T)->rchild));
}
}
Boolean isJust()
{
遍历所有节点 //*
{
If(!条件)
return false;
}
return true;
}
遍历树:
Boolean isJust()
{
遍历所有节点
{
If(!条件)
return false;
}
return true;
}
在二叉树的先序遍历中,“遍历所有节点”即
Boolean isJust(tree*T)
{ if(T!=NULL)
{
visit();
isJust( T->lchild);
isJust(T->rchild);
}
}
而伪代码之中
{
If(!条件)
return false;
}
就相当于visit函数;
根据前面分析的判断条件,构造visit函数:
由前面的算法,函数需要树节点指针*T,标签变量flag(用以记录前面是否出现NULL节点,初始值为0),当前遍历的层数level以及树的深度n四个变量;
“分析”中的第2条可以归结为:
在第k-1层,如果出现了空节点,标识变量flag置1;
if(level==n-1) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
}
如果在前面已经出现了空节点的情况下又出现了非空节点,这就不是完全二叉树:
if(level==n-1) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
if(flag==1&&(T->rchild!=NULL||T->lchild!=NULL))
{return false;}
}
第1条可以归结为:
在第1到k-2层,如果左孩子右孩子为空,则不是完全二叉树:
if(level==n-1) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
if(flag==1&&(T->rchild!=NULL||T->lchild!=NULL))
{return false;}
}
else if(level!=n)
{if(T->rchild==NULL&&T->lchild==NULL)
{return false;}
}
把visit函数装入原函数中,同时调整原函数的形参列表:
Boolean isJust(tree*T,int flag,int level,int n)
{ if(!T)
{
if(level==n) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
if(flag==1&&(T->rchild!=NULL||T->lchild!=NULL))
{return false;}
}
else if(level!=n+1)
{if(T->rchild==NULL&&T->lchild==NULL)
{return false;}
}
isJust( T->lchild,flag,level+1,n);
isJust(T->rchild,flag,level+1,n);
}
return true;
}
这里判断是不是完全二叉树的isJust()函数仍然不符合要求,无论是什么二叉树返回的结果都是真,问题就出在递归的形式上,每一层递归返回的结果之间没有关系,仅仅是深入递归而已,深一层递归返回的结果对上一层无影响。就算是递归深入到最后返回的结果是假,也只是悄悄地返回,然后执行下面一句:return true;
由分析知:此处递归的特点是“符合条件就一直递归遍历下去,有一个不符合就停止递归返回,并返回“假”,能够全部遍历则为真”。遍历到最后若为“假”,此结果要能返回到最表层,若为真,直接跳出递归执行下一句即可。
一次作如下修改:
bool justTree(tree *T,int flag,int level,int n)
{
if(T!=NULL)
{
if(level==n-1) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
if(flag==1&&(T->rchild!=NULL||T->lchild!=NULL))
{return false;}
}
else if(level!=n)
{if(T->rchild==NULL||T->lchild==NULL)
{return false;}
}
if(!justTree(T->lchild,flag,level+1,n)) return false;
if(!justTree(T->rchild,flag,level+1,n)) return false;
}
return true;
}
最后一个问题,怎么获得所创建二叉树的深度:
首先,在创建二叉树的同时记录当前的深度,用的是上一题向递归函数传递参数的办法;
void CreateTree(tree**T,int level)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
CreateTree(&((*T)->lchild),level+1);
CreateTree(&((*T)->rchild),level+1);
}
}
另外向函数传递一个记录深度的变量depth,因为要在函数里改变他的值,所以传的是地址。将这个变量的初始值设为0,用它记录所有当前深度level中的最大值就是这棵树的深度了。
void CreateTree(tree**T,int present_level,int *depth)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
if(present_level>*depth)
{*depth=present_level;}//找到最大深度靠的就是这一句
CreateTree(&((*T)->lchild),present_level+1,depth);
CreateTree(&((*T)->rchild),present_level+1,depth);
}
}
在主函数中用变量depth记录树的深度,先创建一个二叉树并传入depth变量的地址,然后调用判断是否为完全二叉树的函数justTree(),将已记录有树的深度的变量depth传入,然后打印判断结果,如果是完全二叉树则打印“1”,否则打印“0”;
代码清单:
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
char data;
struct node *lchild,*rchild;
}tree;
void CreateTree(tree**T,int present_level,int *depth);
bool justTree(tree *T,int flag,int level,int n);
main()
{
tree *T;
int depth=0;
CreateTree(&T,1,&depth);
printf("%d",justTree(T,0,1,depth));
}
void CreateTree(tree**T,int present_level,int *depth)
{
char c;
scanf("%c",&c);
if(c==' ')
{(*T)=NULL;}
else
{ (*T)=(tree*)malloc(sizeof(tree));
(*T)->data=c;
if(present_level>*depth)
{*depth=present_level;}
CreateTree(&((*T)->lchild),present_level+1,depth);
CreateTree(&((*T)->rchild),present_level+1,depth);
}
}
bool justTree(tree *T,int flag,int level,int n)
{
if(T!=NULL)
{
if(level==n-1) //在第k-1层
{
if(T->rchild==NULL||T->lchild==NULL)
{ flag=1;}
if(flag==1&&(T->rchild!=NULL||T->lchild!=NULL))
{return false;}
}
else if(level!=n)
{if(T->rchild==NULL||T->lchild==NULL)
{return false;}
}
if(!justTree(T->lchild,flag,level+1,n)) return false;
if(!justTree(T->rchild,flag,level+1,n)) return false;
}
return true;
}