数据结构编程笔记十五:第六章 树和二叉树 树和二叉树的转换算法实现

上次已经介绍了递归算法以及二叉树的基本操作,最重要的就是二叉树的遍历算法。这次主要是介绍树的孩子兄弟表示法以及树和二叉树的转换。

还是老规矩:

程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git

本次的程序重点是实现树和二叉树的转换。虽然没有把树的全部操作都实现,但是还是要贴出树的ADT定义,以便大家了解树的定义。

ADT Tree{ 
 数据对象D:D是具有相同特性的数据元素的集合。  
  数据关系R:若D为空集,则称为空树; 
       若D仅含有一个数据元素,则R为空集,否则R={H},H是如下二元关系: 
       (1)在D中存在唯一的称为根的数据元素root,它在关系H下无前驱;  
       (2)若D-{root}≠Φ,则存在D-{root}的一个划分D1,D2,D3, „,Dm(m>0),
          对于任意j≠k(1≤j,k≤m)有Dj∩Dk=Φ,且对任意的i(1≤i≤m),
          唯一存在数据元素xi∈Di有<root,xi>∈H;
       (3)对应于D-{root}的划分,H-{<root,xi>,„,<root,xm>}有唯一的一个划分
          H1,H2,„,Hm(m>0),对任意j≠k(1≤j,k≤m)有Hj∩Hk=Φ,且对任意i
          (1≤i≤m),Hi是Di上的二元关系,(Di,{Hi})是一棵符合本定义的树,
          称为根root的子树。 
 基本操作P: 
    InitTree(&T); 
      操作结果:构造空树T。 
    DestroyTree(&T); 
      初始条件:树T存在。 
      操作结果:销毁树T。 
    CreateTree(&T,definition); 
      初始条件:definition给出树T的定义。 
      操作结果:按definition构造树T。 
    ClearTree(&T); 
      初始条件:树T存在。 
      操作结果:将树T清为空树。 
    TreeEmpty(T); 
      初始条件:树T存在。 
      操作结果:若T为空树,则返回TRUE,否则返回FALSE。 
    TreeDepth(T); 
      初始条件:树T存在。 
      操作结果:返回T的深度。 
    Root(T); 
      初始条件:树T存在。 
      操作结果:返回T的根。 
    Value(T,cur_e); 
      初始条件:树T存在,cur_e是T中某个结点。 
      操作结果:返回cur_e的值。 
    Assign(T,cur_e,value); 
      初始条件:树T存在,cur_e是T中某个结点。 
      操作结果:结点cur_e赋值为value。 
    Parent(T,cur_e); 
      初始条件:树T存在,cur_e是T中某个结点。 
      操作结果:若cur_e是T的非根结点,则返回它的双亲,否则函数值为“空”。 
    LeftChild(T,cur_e); 
      初始条件:树T存在,cur_e是T中某个结点。 
      操作结果:若cur_e是T的非叶子结点,则返回它的最左孩子,否则返回“空”。 
    RightSibling(T,cur_e); 
      初始条件:树T存在,cur_e是T中某个结点。 
      操作结果:若cur_e有右兄弟,则返回它的右兄弟,否则返回“空”。 
    InsertChild(&T,&p,I,c); 
      初始条件:树T存在,p指向T中某个结点,1≤i≤p指结点的度+1,
              非空树c与T不相交。 
      操作结果:插入c为T中p指结点的第i棵子树。 
    DeleteChild(&T,&p,i); 
      初始条件:树T存在,p指向T中某个结点,1≤i≤p指结点的度。 
      操作结果:删除T中p所指结点的第i棵子树。 
    TraverseTree(T,visit()); 
      初始条件:树T存在,visit是对结点操作的应用函数。 
      操作结果:按某种次序对T的每个结点调用函数visit( )一次且至多一次。
               一旦visit( )失败,则操作失败。 
}ADT Tree

二叉树的ADT定义和实现请参考上一篇文章:
数据结构编程笔记十四:第六章 树和二叉树 二叉树基本操作及四种遍历算法的实现
http://blog.csdn.net/u014576141/article/details/77518855

树采用孩子兄弟表示法,孩子兄弟表示法使用的是二叉链表存储结构,二叉树也采用二叉链表的存储结构。所以树和二叉树的转换就是基于相同存储结构的不同翻译。树转换为二叉树需要将孩子结点翻译为二叉树的左孩子,将兄弟结点翻译成二叉树的右孩子。原理如图所示:
这里写图片描述

本次的代码用到了二叉树的二叉链表实现,顺序栈的实现和链队列的实现。和上次的程序一样,这些源文件必须在同一目录下编译。我把其他代码放在总结后面,想看的童鞋自己去看。

接下来看看树和二叉树转换的代码:

//>>>>>>>>>>>>>>>>>>>>>>>>>引入头文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<

#include <stdio.h>       //使用了标准库函数 
#include <stdlib.h>      //使用了动态内存分配函数 
#include "BiTree.cpp"    //引入二叉树的实现 

//>>>>>>>>>>>>>>>>>>>>>>>自定义符号常量<<<<<<<<<<<<<<<<<<<<<<<<<< 

#define OVERFLOW -2          //内存溢出错误常量
#define OK 1                 //表示操作正确的常量 
#define ERROR 0              //表示操作错误的常量
#define TRUE 1               //表示逻辑真的常量 
#define FALSE 0              //表示逻辑假的常量

//>>>>>>>>>>>>>>>>>>>>>>>自定义数据类型<<<<<<<<<<<<<<<<<<<<<<<<<<

typedef int  Status;      //函数返回值状态码类型 
typedef char TElemType;   //树中结点元素类型 

//-------------------树的孩子兄弟表示法----------------------- 
typedef struct CSNode{
     TElemType data;  //数据域,存储结点名称 
     struct CSNode *firstchild, *nextsibling;  //孩子指针域和兄弟指针域 
} CSNode, *CSTree;

//-------------------------------树的主要操作--------------------------------

/*
    函数:CreateCSTree
    参数:CSTree &CT 树的引用 
    返回值:状态码,操作成功返回OK,否则返回ERROR 
    作用:按先根次序输入树中结点的值(一个字符),空格字符表示空树,
           构造二叉链表表示树T
*/
Status CreateCSTree(CSTree &CT){

    //ch保存从键盘接收的字符 
    char ch;

    //从键盘接收字符 
    ch = getchar();

    //用户输入了空格,表示空子树 
    if(ch == ' ') { 
        CT = NULL;
    }//if 
    else{ //用户输入的不是空格,需要生成新的结点

        //分配根结点空间 
        //if(!(CT = (CSNode *)malloc(sizeof(CSNode))))
        //等效于
        //CT = (CSNode *)malloc(sizeof(CSNode))
        //if(!CT) <=> if(CT == NULL)
        if(!(CT = (CSNode *)malloc(sizeof(CSNode)))){
            printf("内存分配失败!\n");
            exit(OVERFLOW); 
        }//if

        //生成根结点 
        CT->data = ch;

        //构建左子树
        CreateCSTree(CT->firstchild); 

        //构建右子树 
        CreateCSTree(CT->nextsibling);
    }//else

    //操作成功 
    return OK; 
}//CreateCSTree

/*
    函数:ExchangeToBiTree
    参数:CSTree &CT 树的引用
          BiTree &T 二叉树的引用 
    返回值:状态码,操作成功返回OK,否则返回ERROR 
    作用:将一棵用二叉链表表示的树转换为二叉树
*/
Status ExchangeToBiTree(CSTree &CT, BiTree &T){

    //若树的根结点为空,那么转换成的二叉树根结点也是空 
    if(!CT) { //if(CT) <=> if(CT != NULL)
        T = NULL;
    }//if 
    else{
        //分配新的结点空间
        //if(!(T = (BiNode *)malloc(sizeof(BiNode))))
        //相当于以下两行代码
        //T = (BiNode *)malloc(sizeof(BiNode));
        //if(!T) <=> if(T == NULL) 
        if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
            printf("内存分配失败!\n");
            exit(OVERFLOW); 
        }//if

        //拷贝树中对应结点到二叉树 
        T->data = CT->data;

        //将树的孩子转换为二叉树的左孩子     
        ExchangeToBiTree(CT->firstchild, T->lchild);

        //将树的兄弟转换为二叉树的右孩子  
        ExchangeToBiTree(CT->nextsibling,T->rchild);  
    }//else 

    //操作成功 
    return OK; 
}//ExchangeToBiTree

/*
    函数:DestoryTree
    参数:CSTree &CT 树的引用 
    返回值:无
    作用:按照树的定义递归地销毁树
*/
void DestoryCSTree(CSTree &CT){

    //非空树
    if(CT){  //if(CT) <=> if(CT != NULL)

        //孩子子树非空,递归的销毁孩子子树 
        //if(CT->firstchild) <=> if(CT->firstchild != NULL) 
        if(CT->firstchild) {
           DestoryCSTree(CT->firstchild);
        }//if

        //兄弟子树非空,递归的销毁兄弟子树
        //if(CT->nextsibling) <=> if(CT->nextsibling != NULL)
        if(CT->nextsibling) { 
           DestoryCSTree(CT->nextsibling);
        }//if

        //释放根结点
        free(CT);

        //指针置空 
        CT = NULL; 
    }//if  
}//DestoryTree

/*
    函数:DestoryBiTree
    参数:BiTree &T 二叉树的引用 
    返回值:无 
    作用:按照二叉树定义递归地销毁二叉树
*/
void DestoryBiTree(BiTree &T){

    //非空树 
    if(T){  //if(T) <=> if(T != NULL)

        //左子树非空,递归的销毁左子树
        if(T->lchild)  {
            DestoryBiTree(T->lchild);
        }//if 

        //右子树非空,递归的销毁右子树
        if(T->rchild) {
            DestoryBiTree(T->rchild);
        }//if 

        //释放根结点
        free(T);

        //指针置空 
        T = NULL; 
    }//if 
}//DestoryTree

/*
    函数:DestoryTree
    参数:CSTree &CT 树的引用 
          BiTree &T  二叉树的引用 
    返回值:无
    作用:销毁树和二叉树
*/
void DestoryTree(CSTree &CT, BiTree &T){

    //销毁树 
    DestoryCSTree(CT);

    //销毁二叉树 
    DestoryBiTree(T);

    printf("\n->生成的树和二叉树已被销毁!"); 
}//DestoryTree

//-----------------------------主函数----------------------------------- 
int main(int argc,char *argv[]){
    printf("----------------------------------  树的应用  ----------------------------------\n");
    BiTree T=NULL;     //声明一棵二叉树
    CSTree CT=NULL;    //声明一棵普通树
    printf("         ---------------------------树的建立----------------------              \n");
    printf("->请按树的先根次序输入序列,如有空子树,用空格填充,完成后输入回车确认\n"); 
    CreateCSTree(CT);
    printf("         ---------------------------树的转换----------------------              \n");
    printf("->正在将输入的树转换为其对应的二叉树...\n");
    ExchangeToBiTree(CT,T); 
    printf("->转换操作执行完毕!\n");
    printf("\n         -------------------------二叉树的遍历--------------------              ");
    printf("\n\n先序遍历递归  算法结果:"); PreOrderTraverse(T,PrintElement);
    printf("\n\n中序遍历递归  算法结果:"); InOrderTraverse(T,PrintElement);
    printf("\n\n后序遍历递归  算法结果:"); PostOrderTraverse(T,PrintElement); 
    printf("\n\n先序遍历非递归算法结果:"); PreOrderTraverse1(T,PrintElement);
    printf("\n\n中序遍历非递归算法结果:"); InOrderTraverse1(T,PrintElement);
    printf("\n\n后序遍历非递归算法结果:"); PostOrderTraverse1(T,PrintElement);
    printf("\n\n层序遍历非递归算法结果:"); LevelOrderTraverse1(T,PrintElement); 
    printf("\n         -------------------------二叉树的信息--------------------              ");
    printf("\n该二叉树的高度:%d",BiTreeDepth(T)); 
    LeafNodeNum(T);
    printf("\n二叉树中叶子结点的个数:%d", LNM);
    printf("\n二叉树总结点数:%d",NodeSubNum(T) );
    printf("\n\n         -------------------------  树的销毁  --------------------              ");
    DestoryTree(CT, T); 
    printf("\n->算法演示结束!"); 
    system("pause"); 
    return 0;
}//main

程序测试使用的是这样一棵树:
这里写图片描述

大家可以手工写出这棵树转换成二叉树之后的遍历结果,然后检验这个结果对不对。

以下是程序测试时的输入和输出:

----------------------------------  树的应用  ----------------------------------

         ---------------------------树的建立----------------------

->请按树的先根次序输入序列,如有空子树,用空格填充,完成后输入回车确认
ABE*F**C*DGHI*J*K******↙   
    //说明:此处的*是空格,为方便确认输入了几个空格将空格替换成*,测试输入时请将*改回空格
            ↙表示回车确认    输入(可直接复制,不要复制↙):ABE F  C DGHI J K      ↙
         ---------------------------树的转换----------------------

->正在将输入的树转换为其对应的二叉树...

->转换操作执行完毕!

         -------------------------二叉树的遍历--------------------


先序遍历递归  算法结果: A  B  E  F  C  D  G  H  I  J  K

中序遍历递归  算法结果: E  F  B  C  I  J  K  H  G  D  A

后序遍历递归  算法结果: F  E  K  J  I  H  G  D  C  B  A

先序遍历非递归算法结果: A  B  E  F  C  D  G  H  I  J  K

中序遍历非递归算法结果: E  F  B  C  I  J  K  H  G  D  A

后序遍历非递归算法结果: F  E  K  J  I  H  G  D  C  B  A

层序遍历非递归算法结果: A  B  E  C  F  D  G  H  I  J  K

         -------------------------二叉树的信息--------------------

该二叉树的高度:9
二叉树总结点数:11

         -------------------------  树的销毁  --------------------
->生成的树和二叉树已被销毁!
->算法演示结束!请按任意键继续. . .

总结:树和二叉树的转换其实就是基于相同存储结构的不同翻译。

下次的文章将介绍线索二叉树的实现。希望大家继续关注我的博客。再见!

附:
二叉树实现精简版。源文件:BiTree.cpp

//>>>>>>>>>>>>>>>>>>>>>>>>>自定义符号常量<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

#define STACK_INIT_SIZE 50   //顺序栈存储空间初始分配量 
#define STACKINCREMENT 10    //顺序栈存储空间分配增量  
#define OVERFLOW -2          //内存溢出错误常量
#define OK 1                 //表示操作正确的常量 
#define ERROR 0              //表示操作错误的常量
#define TRUE 1               //表示逻辑真的常量 
#define FALSE 0              //表示逻辑假的常量

//>>>>>>>>>>>>>>>>>>>>>>>>>自定义数据类型<<<<<<<<<<<<<<<<<<<<<<<<<<<<

typedef int  Status;      //状态码为int类型,用于保存操作结果(1成功0失败) 
typedef char TElemType;   //二叉树节点数据域的元素类型 

//----------------二叉树的二叉链表存储表示-------------------- 
typedef struct BiNode{
    TElemType  data;
    struct BiNode  *lchild,*rchild;   //孩子结点指针 
}BiNode,*BiTree;

//--------引入栈和队列的实现(实际上应该放在头部,由于编译原因,只好这样了)---------------- 
#include "Queue.cpp"     //引入队列的实现 
#include "Stack.cpp"     //引入栈的实现 

//---------------------二叉树的主要操作--------------------------

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>1.构造二叉树<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 

/*
    函数:CreateBiTree
    参数:BiTree &T 二叉树引用 
    返回值:状态码,操作成功返回OK,否则返回ERROR 
    作用:按先序次序输入二叉树中结点的值(一个字符),空格字符表示空树,
          递归的构造二叉链表表示二叉树T
*/
Status CreateBiTree(BiTree &T){ 

    //ch存储从键盘接收的字符 
    char ch;

    //从键盘接收字符 
    ch = getchar();

    //判断输入的字符是否是空格 
    if(ch == ' ') { //输入空格表示结点为空 
        T = NULL;
    }//if 
    else{ //不是空格,按正常结点对待

        //申请结点空间
        //if(!(T = (BiNode *)malloc(sizeof(BiNode))))
        //等效于以下两行代码
        //T = (BiNode *)malloc(sizeof(BiNode));
        //if(!(T = (BiNode *)malloc(sizeof(BiNode)))) 
        if(!(T = (BiNode *)malloc(sizeof(BiNode)))){
            printf("内存分配失败!\n");
            exit(OVERFLOW); 
        }//if

        //生成根结点
        T->data = ch;

        //递归的构建左子树      
        CreateBiTree(T->lchild);

        //递归的构建右子树 
        CreateBiTree(T->rchild);
    }//else

    //操作成功 
    return OK; 
}//CreateBiTree

//>>>>>>>>>>>>>>>>>>>>2.二叉树的遍历(4种方法)<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函数:Print
    参数:TElemType e 被访问的元素 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
          该函数使用时需要配合遍历函数一起使用。 
*/
Status PrintElement(TElemType e) {

    //采用控制台输出的方式访问元素 
    printf(" %c ", e);

    //操作成功 
    return OK;
}//PrintElement

//------------------------递归算法----------------------------- 

/*
    函数:PreOrderTraverse
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit 
*/
Status PreOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根节点存在 
    //if(T) <=> if(T != NULL)
    if(T){

        //1.访问根结点
        //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
        if(Visit(T->data)) {

            //2.访问左孩子(左子树) 
            if(PreOrderTraverse(T->lchild, Visit)) {

                //3.访问右孩子(访问右子树) 
                if(PreOrderTraverse(T->rchild, Visit)) {
                    return OK;
                }//if 
            }//if
        }//if

        return ERROR;
    }//if
    else { 
        return OK;
    }//else 
}//PreOrderTraverse

/*
    函数:InOrderTraverse
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          中序遍历二叉树T的递归算法,对每个数据元素调用函数Visit 
*/
Status InOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根节点存在 
    if(T){  //if(T)  <=>  if(T != NULL)

        //1.访问左子树 
        if(InOrderTraverse(T->lchild,Visit)) {

            //2.访问根节点 
            //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
            if(Visit(T->data)) {

                //3.访问右子树 
                if(InOrderTraverse(T->rchild,Visit)) {

                    return OK;
                }//if
            }//if 
        }//if 

        return ERROR;
    }//if
    else {
        return OK;
    }//else 
}//InOrderTraverse 

/*
    函数:PostOrderTraverse
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          后序遍历二叉树T的递归算法,对每个数据元素调用函数Visit 
*/
Status PostOrderTraverse(BiTree T, Status(* Visit)(TElemType)){

    //根结点存在 
    if(T){  //if(T)  <=>  if(T != NULL)

        //1.访问左子树 
        if(PostOrderTraverse(T->lchild, Visit)) {

            //2.访问右子树 
            if(PostOrderTraverse(T->rchild, Visit)) {

                //3.访问根结点 
                //if(Visit(T->data)) <=> if(Visit(T->data) != ERROR)
                if(Visit(T->data)) {
                    return OK;
                }//if
            }//if
        }//if

        return ERROR;
    }//if
    else return OK;
}//PostOrderTraverse

//-----------------------非递归遍历算法---------------------------

/*
    函数:PreOrderTraverse1
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          先序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit 
*/
Status PreOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){

    //二叉树非递归遍历需要借用栈来保存回溯点 
    Stack S;

    //初始化栈 
    InitStack(S);

    //工作指针p指向二叉树根结点 
    BiTree p = T;

    //遍历继续的条件:工作指针p不为空或栈不为空
    //while(p || !(StackIsEmpty(S))) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !(StackIsEmpty(S))){

        //根结点存在 
        if(p){

            //1.访问根结点 
            //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
            if(!Visit(p->data)) { 
                return ERROR;
            }//if 

            //根指针进栈
            Push(S, p);

            //2.遍历左子树
            p = p->lchild;
        }//if
        else{
            //根指针退栈
            Pop(S, p);

            //3.遍历右子树
            p = p->rchild;
        }//else
    }//while

    //销毁栈
    DestoryStack(S); 

    //操作成功 
    return OK;
} //PreOrderTraverse1

/*
    函数:InOrderTraverse1
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit 
*/
Status InOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){ 

    //二叉树非递归遍历需要借用栈来保存回溯点
    Stack S;

    //初始化栈 
    InitStack(S);

    //工作指针p指向根结点 
    BiTree p = T;

    //遍历继续的条件:工作指针p不为空或栈不为空
    //while(p || !(StackIsEmpty(S))) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !(StackIsEmpty(S))) {

        //根结点不为空 
        if(p){

            //根指针进栈
            Push(S, p); 

            //1.遍历左子树
            p = p->lchild;
        }//if
        else{
            //根指针退栈
            Pop(S, p);

            //2.访问根结点
            //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
            if(!Visit(p->data)) {  
                return ERROR;
            }//if

            //3.遍历右子树
            p = p->rchild; 
        }//else
    }//while

    //销毁栈
    DestoryStack(S); 

    //操作成功 
    return OK;
} //InOrderTraverse1

/*
    函数:PostOrderTraverse1
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          后序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit 
*/
Status PostOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){

    //p和q都是工作指针
    //p指向当前遍历的结点,q指向p最近一次遍历的结点 
    BiTree p = T, q = NULL;

    //二叉树非递归遍历需要借用栈来保存回溯点
    Stack s;

    //初始化栈 
    InitStack(s);

    //遍历继续的条件:工作指针p不为空或栈不为空
    //while(p || !StackIsEmpty(S)) 
    //<=> while(p != NULL || StackIsEmpty(S) != 1)
    while(p || !StackIsEmpty(s)) {

        //顺着树的根,一直走左分支,直到遇到最左分支的尽头(叶子节点的左孩子)。 
        while(p){

            //根结点入栈 
            Push(s, p);

            //访问左子树 
            p = p->lchild;
        }//while

        //重置指针q的值为NULL 
        q = NULL;

        //栈不为空 
        while(!StackIsEmpty(s)){

            //p指向栈顶元素 
            GetTop(s, p);

            //这个条件表示p指向了叶子结点或者p的左右子树均被遍历过 
            if(p->rchild == NULL || p->rchild == q){

                //访问根结点 
                //if(Visit(p->data)) <=> if(Visit(p->data) != ERROR)
                if(!Visit(p->data)) { 
                    return ERROR;    
                }//if

                if(p == T) {
                    return ERROR;
                }//if

                //q指向的是p的上一次遍历过的结点
                q = p;

                //根指针出栈 
                Pop(s, p);
            }//if
            else{

                //访问右子树 
                p = p->rchild;

                //退出内层循环 
                break;              
            }//else
        }//while
    }//while

    //销毁栈
    DestoryStack(s); 

    //操作成功 
    return OK;
} //PostOrderTraverse1

/*
    函数:LevelOrderTraverse1
    参数:BiTree T 二叉树T
          Status(* Visit)(TElemType) 函数指针,指向元素访问函数 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:采用二叉链表存储结构,Visit是对数据元素操作的应用函数
          层序遍历二叉树T的算法,对每个数据元素调用函数Visit 
*/
Status LevelOrderTraverse1(BiTree T, Status(* Visit)(TElemType)){ 

    //层序遍历需要用到队列 
    Queue Q;

    //工作指针p指向根结点 
    BiTree p = T;

    //根结点不为空 
    if(T){ //if(T) <=>  if(T != NULL)
        //初始化队列
        InitQueue(Q);

        //根结点入队列
        EnQueue(Q, T);

        //队列不空 
        //while(!QueueEmpty(Q)) <=> while(QueueEmpty(Q) == 0)
        while(!QueueEmpty(Q)){

            //根结点出队 
            DeQueue(Q, p);

            //访问根结点 
            if(!Visit(p->data)) { 
               return ERROR;
            }//if

            //左孩子不为空 
            if(p->lchild) {
                //左孩子入队列 
                EnQueue(Q, p->lchild);   
            }//if

            if(p->rchild) {
                //右孩子入队列 
                EnQueue(Q, p->rchild);
            }//if 
       }//while

       //输出换行,使显示美观 
       printf("\n");

       //队列用完之后要销毁,释放其内存空间 
       DestoryQueue(Q); 
    }//if

    //操作成功 
    return OK;
} //LevelOrderTraverse1

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>3.二叉树的信息<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*
    函数:BiTreeDepth
    参数:BiTree T 二叉树T
    返回值:若二叉树T存在,返回T的深度(高度),否则返回0
    作用:若二叉树T存在,递归地求二叉树T的深度 
*/
int BiTreeDepth(BiTree T){ 

    //Thigh是二叉树高度,leftThigh是左子树高度,rightThigh是右子树高度
    int Thigh, leftThigh, rightThigh;

    //根结点为空,树高为0 
    if(!T) {
        return 0;
    }//if
    else{
        //根结点不为空,则递归的计算树的高度

        //递归的求出左子树高度
        leftThigh = BiTreeDepth(T->lchild);

        //递归的求出右子树高度 
        rightThigh = BiTreeDepth(T->rchild);

        //左右子树可能高度不相等,按照树的高度定义
        //应取左子树和右子树中高度较大者作为树的高度 
        if(leftThigh >= rightThigh) { 
           Thigh = leftThigh + 1;
        }//if 
        else { 
           Thigh = rightThigh + 1;
        }//else
    }//else

    //返回树的高度 
    return Thigh;
}//BiTreeDepth

//全局变量LNM记录了二叉树叶子节点的个数 
int LNM = 0;

/*
    函数:LeafNodeNum
    参数:BiTree T 二叉树T
    返回值:若二叉树T存在,返回T的叶子结点个数,否则返回0
    作用:递归求二叉树叶子结点的个数 
*/
int LeafNodeNum(BiTree T){ 

    //叶子结点的特征是:左孩子和右孩子指针域均为NULL 
    if(T->lchild == NULL && T->rchild == NULL) { //当前结点是叶子结点 
        LNM++;
    }//if
    else {
        //左孩子不为空 
        if(T->lchild != NULL) {

            //递归的统计左子树中叶子结点的数目 
            LeafNodeNum(T->lchild);
        }//if

        //右孩子不为空 
        if(T->rchild != NULL) {

            //递归的统计右子树中叶子结点的数目 
            LeafNodeNum(T->rchild);
        }//if
    }//else
}//LeafNodeNum

/*
    函数:NodeSubNum
    参数:BiTree T 二叉树T
    返回值:若二叉树T存在,返回T的总结点个数,否则返回0
    作用:统计二叉树的总结点个数
*/
int NodeSubNum(BiTree T){

    if(!T) {
        return 0;  //空树或空子树
    }//if 
    else {
        //二叉树总结点数 = 左子树总结点数 + 右子树总结点数 + 自身(1) 
        return NodeSubNum(T->lchild) + NodeSubNum(T->rchild) + 1;
    } 
}//NodeSubNum

顺序栈的实现精简版。源文件:Stack.cpp

//-------------------栈的顺序存储表示------------------------- 

typedef BiTree SElemType;   //栈的元素为二叉树指针类型 
typedef struct {          //栈的顺序存储表示                        
    SElemType *base;            //栈底指针,在栈构造之前和销毁之后,base的值为NULL 
    SElemType *top;             //栈顶指针
    int stacksize;              //当前已分配的存储空间,以元素为单位 
}Stack; 

//--------------------------栈的相关函数(供非递归后序遍历使用)----------------------------
/*
    函数:InitStack_Sq
    参数:Stack &S 顺序栈引用  
    返回值:状态码,OK表示操作成功 
    作用:构造一个空的顺序栈 
*/
Status InitStack(Stack &S){

    //动态申请顺序栈的内存空间,并检查内存空间是否成功分配
    //if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))))
    //这句代码相当于以下两行代码:
    //S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType));
    //if(!S.base)  <=>  if(S.base == NULL) 
    if(!(S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType)))){
        printf("内存分配失败,程序即将退出!\n");
        exit(OVERFLOW);
    }//if

    //由于刚动态分配完的栈是空栈,所以栈顶指针和栈底指针都指向栈底   
    S.top = S.base;

    //栈的大小就是栈的初始化大小参数STACK_INIT_SIZE 
    S.stacksize = STACK_INIT_SIZE;

    //操作成功 
    return OK; 
}//InitStack_Sq

/*
    函数:DestoryStack_Sq
    参数:Stack &S 顺序栈引用  
    返回值:状态码,OK表示操作成功 
    作用:释放顺序栈S所占内存空间 
*/
Status DestoryStack(Stack &S){

    //栈底指针保存的是顺序栈内存空间的首地址 
    free(S.base);

    //操作成功 
    return OK; 
}//DestoryStack_Sq

/*
    函数:StackIsEmpty_Sq
    参数:Stack S 顺序栈S 
    返回值:若顺序栈S是空栈返回1,否返回0 
    作用:判断顺序栈S是否为空栈
*/
Status StackIsEmpty(Stack S){

    //栈顶指针和栈底指针都指向栈底表示此栈是空栈 
    return S.top == S.base; 
}//StackIsEmpty_Sq

/*
    函数:ReallocStack_Sq
    参数:Stack &S 顺序栈S引用 
    返回值:状态码,操作成功返回OK,否则返回ERRROR
    作用:将栈S扩容,每扩容一次,栈的大小增加STACKINCREMENT
*/
Status ReallocStack(Stack &S){

    //为顺序栈重新分配内存(扩容),扩展的空间大小是STACKINCREMENT
    /*if(!(S.base = (SElemType *)realloc(S.base, 
                (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType))))
      这句代码相当于:
      S.base = (SElemType *)realloc(S.base, 
                        (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType));
      if(!S.base) <=> if(S.base == NULL)
    */
    if(!(S.base = (SElemType *)realloc(S.base, 
               (STACK_INIT_SIZE + STACKINCREMENT) * sizeof(SElemType)))){
        printf("内存分配失败,程序即将退出!\n");
        exit(OVERFLOW);
    }//if

    //由于扩容前栈已经满了,所以栈顶指针位置就是栈底指针+原来栈的大小 
    S.top = S.base + S.stacksize;

    //扩容后,栈的大小增加了STACKINCREMENT 
    S.stacksize += STACKINCREMENT;

    //操作成功 
    return OK; 
}//ReallocStack_Sq

/*
    函数:Push_Sq
    参数:Stack &S 顺序栈引用
          SElemType e 被插入的元素e 
    返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR
    作用:(入栈、压栈)插入元素e为新的栈顶元素
*/
Status Push(Stack &S, SElemType e){ 

    //入栈时发现栈满了,就要追加存储空间(扩容) 
    if(S.top - S.base >= S.stacksize) {  

        //调用扩容函数
        ReallocStack(S);
    }//if

    //插入前,栈顶指针指向当前栈顶元素的下一个位置 
    //将e赋值给栈顶指针所指存储空间(插入元素e),栈顶指针后移
    //*S.top++ = e;  <=>  *(S.top) = e;  S.top++; 
    *S.top++ = e;

}//Push_Sq

/*
    函数:Pop_Sq
    参数:Stack &S 顺序栈引用
          SElemType &e 带回被删除的元素值e 
    返回值:删除成功返回OK,否则返回ERRROR
    作用:(出栈,弹栈)若栈不空,则删除S的栈顶元素,用e返回其值
*/
Status Pop(Stack &S, SElemType &e){ 

    //在空栈中执行出栈操作没有意义,所以要判断栈是否为空
    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用 
    //S.base != NULL判断栈是否为空 
    if(StackIsEmpty(S)) { 
         return ERROR;
    }//if

    //删除前,栈顶指针指向当前栈顶元素的下一个位置
    //--S.top;之后,栈顶指针刚好指向被删除元素 
    //栈顶指针前移,保存被删除的元素值到e
    //e=*--S.top;  <=>  --S.top;   e=*(S.top);
    e = *--S.top;

    //操作成功 
    return OK; 
}//Pop_Sq

/*
    函数:GetTop
    参数:Stack S 顺序栈S
    返回值:成功获取顺序栈S栈顶元素的值后返回OK,否则返回ERRROR
    作用:用e返回栈顶元素的值,但是栈顶元素不做出栈操作 
*/
Status GetTop(Stack S, SElemType &e){

    //空栈没有栈顶元素,所以要先判断栈是否为空 
    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用 
    //S.base != NULL判断栈是否为空 
    if(StackIsEmpty(S)) { 
         return ERROR;
    }//if

    //注意:栈顶指针指向栈顶元素的下一个位置
    e = *(S.top - 1);  
    /*   注意:此处不能使用“e = *(--S.top); ”的原因 
         1. --S.top自减操作改变了栈顶指针本身的指向,使得该指针向前移动一位,相当于删除了原来栈中的最后一个元素(最后一个元素出栈); 
         2. S.top-1 仅仅表示栈顶指针的上一个位置,并没有改变S.top的值,*(S.top-1)表示取栈顶指针前一个位置的值,即栈顶元素的值  
         3. 这两种写法造成的结果是不同的,如果是纯代数运算,两者没有差别,但在指向数组
            (顺序结构在C语言中是用一维数组描述的)的指针变量运算中,这两个表达式有特殊含义 
            在指针运算中,“指针变量-1 ”表示该指针变量所指位置的前一个位置,
            这种做法并不改变指针变量本身的值。 
            --指针变量   不仅使得该指针指向原来所指位置的上一个位置, 还修改了指针变量本身的值
            在栈中,栈顶指针和栈底指针所指向的位置有特殊的含义,故两者不等价。       
     */ 

     //操作成功 
     return OK; 
}//GetTop_Sq

/*
    函数:StackLength_Sq
    参数:Stack S 顺序栈S 
    返回值:若顺序栈S是空栈返回1,否返回0 
    作用:判断顺序栈S是否为空栈
*/
Status StackLength(Stack S){

    //栈的长度就是栈顶指针和栈底指针之间的元素个数 
    return (S.top - S.base); 
}//StackLength_Sq

/*
    函数:Print
    参数:ElemType e 被访问的元素 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
          该函数使用时需要配合遍历函数一起使用。 
*/
Status Print_Stack(SElemType e){
    printf("%5d  ", e);
    return OK;
}//Print 

/*
    函数:StackTraverse_Sq
    参数:Stack S 顺序栈S 
          Status(* visit)(SElemType) 函数指针,指向元素访问函数。 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:调用元素访问函数按出栈顺序完成顺序栈的遍历,但并未真正执行出栈操作 
*/
Status StackTraverse(Stack S, Status(* visit)(SElemType)) {

    //在空栈中执行遍历操作没有意义,所以要判断栈是否为空
    //注意栈是否为空和栈是否存在不是一个概念,所以不可以用 
    //S.base != NULL判断栈是否为空 
    if(StackIsEmpty(S)) {
        printf("此栈是空栈"); 
        return ERROR;
    }//if

    //调用元素访问函数依次访问栈中的每个元素
    for(int i = 0; i < StackLength(S); ++i){

        //调用元素访问函数,一旦访问失败则退出  
        if(!visit(S.base[i])) {
            return ERROR;
        }//if 
    }//for

    //输出换行,是控制台显示美观 
    printf("\n");

    //操作成功 
    return OK;
}//StackTraverse_Sq

链队列实现精简版。对应源文件:Queue.cpp

//------------------队列的链式存储表示----------------------- 
typedef BiTree QElemType;   //队列元素为二叉树指针类型

typedef struct  QNode{  //链队列的C语言表示                  
     QElemType data;        //数据域 
     struct QNode * next;   //指针域 
}QNode,* QueuePtr;

typedef struct{
    QueuePtr front;  //队头指针 
    QueuePtr rear;   //队尾指针 
}Queue; 

//--------------------------队列的相关函数(供非递归层序遍历使用)----------------------------
/*
    函数:MallocQNode
    参数:无 
    返回值:指向新申请结点的指针 
    作用:为链队列结点申请内存的函数 
*/
QueuePtr MallocQNode(){

    //工作指针p,指向新申请的结点 
    QueuePtr p;

    //if(!(p = (QueuePtr)malloc(sizeof(QNode))))  相当于以下两行代码: 
    //p = (QueuePtr)malloc(sizeof(QNode));
    //if(!p)  <=>  if(p != NULL) 
    //申请结点的内存空间,若失败则提示并退出程序
    if(!(p = (QueuePtr)malloc(sizeof(QNode)))){
        printf("内存分配失败,程序即将退出!\n");
        exit(OVERFLOW);
    }//if

    //返回新申请结点的地址 
    return p;
}//MallocQNode 

/*
    函数:InitQueue
    参数:Queue &Q 链队列引用 
    返回值:状态码,操作成功返回OK 
    作用:构建一个空队列 Q
*/
Status InitQueue(Queue &Q) {

    //申请头结点的内存空间,并使队头和队尾指针同时指向它
    Q.front = Q.rear = MallocQNode();

    //由于头结点刚刚初始化,后面还没有元素结点 
    Q.front->next = NULL;

    //头结点数据域记录了链队列长度
    //由于此时链队列没有数据节点,所以将头结点数据域设为0 
    Q.front->data = 0;

    //操作成功 
    return OK;
}//InitQueue 

/*
    函数:DestoryQueue
    参数:Queue &Q 链队列引用 
    返回值:状态码,操作成功返回OK 
    作用:销毁队列Q
*/
Status DestoryQueue(Queue &Q){

    //从头结点开始向后逐个释放结点内存空间 
    while(Q.front){ //while(Q.front) <=> while(Q.front != NULL)

        //队尾指针指向被删除结点的后继结点 
        Q.rear = Q.front->next;

        //释放Q.front指向的被删除结点的空间 
        free(Q.front);

        //队头指针后移,指向下一个待删除结点 
        Q.front = Q.rear; 
    }//while

    //操作成功 
    return OK;
}//DestoryQueue

/*
    函数:QueueEmpty
    参数:Queue Q 链队列Q 
    返回值:状态码,若Q为空队列,则返回TRUE;否则返回FALSE
    作用:判断队列Q是否为空 
*/
Status QueueEmpty(Queue Q){

    //队头指针和队尾指针均指向链队列头结点表示链队列为空 
    if(Q.rear == Q.front){
        return TRUE; 
    }//if
    else {
        return FALSE; 
    }//else
}//QueueEmpty

/*
    函数:EnQueue
    参数:Queue &Q 链队列Q的引用
          QElemType e  被插入的元素e 
    返回值:状态码,操作成功后返回OK。 
    作用:插入元素e为Q的新的队尾元素
*/
Status EnQueue(Queue &Q, QElemType e){

    //申请一个新的结点,并使p指向这个新结点 
    QueuePtr p = MallocQNode(); 

    //将待插入元素e保存到新结点数据域 
    p->data = e;

    //由于新结点要插在队尾,后面没有其他结点,所以后继指针域的值为NULL 
    p->next = NULL;

    //将新结点链入到队尾 
    //队列要求插入操作只能发生在队尾 
    Q.rear->next = p;

    //修正队尾指针,使之指向p所指向的新插入的结点 
    Q.rear = p;

    //由于插入一个结点,所以存储在头结点中的队列长度+1 
    Q.front->data++;

    //插入操作成功 
    return OK; 
}//EnQueue 

/*
    函数:DeQueue
    参数:Queue &Q 链队列Q的引用
          QElemType &e 带回被删除结点的元素e 
    返回值:状态码,操作成功后返回OK。 
    作用:若队列不空,则删除Q的队头元素,用e返回其值
*/
Status DeQueue(Queue &Q, QElemType &e){

    //注意队列为空和队列不存在的区别,队列为空,头结点一定存在,
    //队列不存在时头结点一定不存在
    //对空队列执行出队操作没有意义,出队操作执行前要先检查队列是否为空 
    if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
        return ERROR;
    }//if

    //工作指针p指向队头第一个结点(不是头结点,是头结点的后继)
    //队列要求删除操作只能发生在队头,所以p指向的就是待删除节点 
    QueuePtr p = Q.front->next;

    //保存被删除结点的值 
    e = p->data;

    //在删除操作执行前修正队头指针的位置,使之在删除结点后指向新的队头结点 
    Q.front->next = p->next;

    //若被删除结点恰好是队尾结点,那么该结点被删除后,队列将会变成空队列
    //此时刚好满足空队列条件:Q.rear == Q.front,所以要修正队尾指针的位置,使之指向头结点 
    if(Q.rear == p) { 
        Q.rear = Q.front;
    }//if

    //在队头指针和队尾指针的位置都调整好了之后就可以
    //放心地释放p指向的结点的内存空间了 
    free(p);

    //由于从队列中删除了一个结点,头结点存储的队列长度应当-1 
    Q.front->data--;

    //操作成功 
    return OK; 
}//DeQueue

/*
    函数:Print
    参数:ElemType e 被访问的元素 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:访问元素e的函数,通过修改该函数可以修改元素访问方式,
          该函数使用时需要配合遍历函数一起使用。 
*/
Status Print_Queue(QElemType e){

    //指定元素的访问方式是控制台打印输出 
    printf("%6.2f    ",e);

    //操作成功 
    return OK;
}//Print

/*
    函数:QueueTraverse
    参数:Queue Q 链队列Q 
          Status (* visit)(QElemType) 函数指针,指向元素访问函数。 
    返回值:状态码,操作成功返回OK,操作失败返回ERROR 
    作用:调用元素访问函数按出队顺序完成链队列的遍历,但并未真正执行出队操作 
*/
Status QueueTraverse(Queue Q, Status (* visit)(QElemType)) {   

    //对空队列进行遍历操作没有意义,所以遍历操作前要先判断队列是否为空 
    //注意队列为空和队列不存在的区别,队列为空,头结点一定存在,
    //队列不存在时头结点一定不存在  
    if(QueueEmpty(Q)) { //if(QueueEmpty(Q)) <=> if(QueueEmpty(Q) != TRUE)
        return ERROR;
    }//if

    //工作指针p指向队头结点 
    QueuePtr p = Q.front->next;

    //从队头结点开始依次访问每个结点,直到队尾 
    while(p) { //while(p)  <=>  while(p != NULL) 

        //调用元素访问函数 
        if(!visit(p->data)) { 
            printf("输出发生错误!\n");
            return ERROR;
        }//if

        //工作指针p后移,指向下一个元素 
        p = p->next;
    }//while

    //输出换行,使结果清楚美观 
    printf("\n");

    //操作成功 
    return OK; 
}//QueueTraverse
  • 25
    点赞
  • 92
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值