数据结构——树(二叉树)

  

树结构的特点:

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 isJusttree*T

{     if(T!=NULL)

{

 visit();

      isJust T->lchild;

      isJustT->rchild;

}

}

而伪代码之中

{

      If(!条件)

     return false;

}

就相当于visit函数;

                 

根据前面分析的判断条件,构造visit函数:

 由前面的算法,函数需要树节点指针*T,标签变量flag(用以记录前面是否出现NULL节点,初始值为0),当前遍历的层数level以及树的深度n四个变量;

“分析”中的第2条可以归结为:

在第k-1层,如果出现了空节点,标识变量flag1

    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条可以归结为:

在第1k-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 isJusttree*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;

      isJustT->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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值