#头歌 数据结构 实验七 二叉树的建立及应用

第1关:二叉树的顺序存储及基本操作

任务描述

本关任务:以顺序结构存储二叉树,编写前序、中序、后序及层次顺序遍历二叉树的算法,并计算二叉树深度、所有结点总数。

相关知识

二叉树的定义

二叉树的递归定义:

  • 二叉树或者是一棵空树。
  • 或者是一棵由一个根结点和两棵互不相交的分别称做根结点的左子树和右子树所组成的非空树,左子树和右子树又同样都是一棵二叉树。

二叉树的逻辑示意图

二叉树与度为2的树的区别:

  • 度为2的树至少有3个结点,而二叉树的结点数可以为0。
  • 度为2的树不区分子树的次序,而二叉树中的每个结点最多有两个孩子结点,且必须要区分左右子树,即使在结点只有一棵子树的情况下也要明确指出该子树是左子树还是右子树。

二叉树的5种形态:

二叉树的五种形态

二叉树的性质

性质1 非空二叉树上叶结点数等于双分支结点数加1。即n0=n2+1。 约定:二叉树上叶结点数为n0,单分支结点数为n1,双分支结点数为n2,

性质2 非空二叉树上第i层上至多有 2i−1 个结点,这里应有i≥1。

性质3 高度为h的二叉树至多有2h−1个结点(h≥1)。

满二叉树:在一棵二叉树中,当第i层的结点数为2i−1个时,则称此层的结点数是满的,当一棵二叉树中的每一层都满时,且叶子结点在同一层上,则称此树为满二叉树。

满二叉树特性:

  1. 除叶子结点以外的其他结点的度皆为2。 

  2. 高度为h的满二叉树中的结点数为2h−1个。

  3. 层序编号:从根结点为1开始,按照层次从小到大、同一层从左到右的次序顺序编号。可以对满二叉树的结点进行连续编号。

满二叉树编号

完全二叉树:在一棵二叉树中,除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干个结点,则称此树为完全二叉树。

完全二叉树特性:

  1. 完全二叉树中至多只有最下边两层结点的度数小于2。

  2. 高度为h的完全二叉树若按层序编号,它与高度为h的满二叉树中结点的编号一一对应。

如下图所示的一棵完全二叉树,它与等高度的满二叉树相比,在最后一层的右边缺少了3个结点。

完全二叉树编号

性质4 对完全二叉树中编号为i的结点(1≤i≤n,n≥1,n为结点数)有: (1)若 i⩽└2n​┘,即2i≤n,则编号为i的结点为分支结点,否则为叶子结点。 (2)若n为奇数,则每个分支结点都既有左孩子结点,也有右孩子结点; 若n为偶数,则编号最大的分支结点只有左孩子结点,没有右孩子结点,其余分支结点都有左、右孩子结点。 (3)若编号为i的结点有左孩子结点,则左孩子结点的编号为2i;若编号为i的结点有右孩子结点,则右孩子结点的编号为(2i+1)。 (4)除树根结点外,若一个结点的编号为i,则它的双亲结点的编号为 └2i​┘,也就是说,当i为偶数时,其双亲结点的编号为2i​,它是双亲结点的左孩子结点,当i为奇数时,其双亲结点的编号为2i−1​,它是双亲结点的右孩子结点。

二叉树性质5

二叉树的遍历

二叉树的遍历是指按一定的次序访问树中的所有结点,使每个结点恰好被访问一次。其中遍历次序保证了二叉树上每个结点均被访问一次且仅有一次。

二叉树常用的遍历有先序(根)遍历中序(根)遍历后序(根)遍历层次遍历

所谓先序、中序、后序,区别在于访问根结点的顺序。 1.先序遍历 若二叉树非空,则: ① 访问根结点; ② 先序遍历左子树; ③ 先序遍历右子树。 先序遍历序列的特点:其第一个元素值为二叉树中根结点值。

2.中序遍历 若二叉树非空,则: ① 中序遍历左子树; ② 访问根结点; ③ 中序遍历右子树。 中序遍历序列的特点:若已知二叉树的根结点值,以该值为界,将中序遍历序列分为两部分,前半部分为左子树的中序遍历序列,后半部分为右子树的中序遍历序列。

3.后序遍历 若二叉树非空,则: ① 后序遍历左子树; ② 后序遍历右子树; ③ 访问根结点。 后序遍历序列的特点:最后一个元素值为二叉树中根结点值。

4.层次遍历 层次遍历是从根结点出发,按照从上向下,同一层从左向右的次序访问所有的结点。采用层次遍历得到的访问结点序列称为层次遍历序列。 层次遍历序列的特点:其第一个元素值为二叉树中根结点值。

例:图1表示一个二叉树

前序遍历:ABCDEF 中序遍历:CBDAFE 后序遍历:CDBFEA 层次遍历:ABECDF

二叉树也可以采用两种存储方式:顺序存储结构和链式存储结构。

二叉树的顺序存储结构

顺序存储一棵二叉树时,就是用一组连续的存储单元存放二叉树中的结点。

由二叉树的性质4可知,对于完全二叉树(或满二叉树),树中结点层序编号可以唯一地反映出结点之间的逻辑关系,所以可以用一维数组按从上到下、从左到右的顺序存储树中所有结点值,通过数组元素的下标关系反映完全二叉树或满二叉树中结点之间的逻辑关系。

一棵完全二叉树的顺序存储结构:

完全二叉树及其相应的顺序存储结构

一般的二叉树的顺序存储结构:

一般二叉树及其相应的顺序存储结构

二叉树顺序存储结构的类型定义如下: typedef ElemType SqBinTree[MaxSize];

其中,ElemType为二叉树中结点的数据值类型,MaxSize为顺序表的最大长度。为了方便运算,通常将下标为0的位置空着,值为#的结点为空结点。  

完全二叉树或满二叉树采用顺序存储结构比较合适,既能够最大可能地节省存储空间,又可以利用数组元素的下标确定结点在二叉树中的位置以及结点之间的关系。

对于一般二叉树,如果它接近于完全二叉树形态,需要增加的空结点个数不多,也可适合采用顺序存储结构。

如果需要增加很多空结点才能将一棵二叉树改造成为一棵完全二叉树,采用顺序存储结构会造成空间的大量浪费,这时不宜用顺序存储结构。例如:

二叉树的顺序存储结构缺点

编程要求

根据提示,在右侧编辑器补充代码。具体要求如下:

  • InitBiTree(SqBiTree &T)//构造空二叉树T。

  • CreateBiTree(SqBiTree &T)//按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T

  • BiTreeEmpty(SqBiTree T)//判断二叉树T是否为空二叉树,若是则返回TRUE,否则FALSE

  • BiTreeDepth(SqBiTree T)//返回二叉树T的深度

  • PreOrderTraverse(SqBiTree T,void(*Visit)(TElemType))//先序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • InOrderTraverse(SqBiTree T,void(*Visit)(TElemType))//中序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • PostOrderTraverse(SqBiTree T,void(*Visit)(TElemType))//后序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • LevelOrderTraverse(SqBiTree T,void(*Visit)(TElemType)) // 层序遍历二叉树

测试说明

平台会对你编写的代码进行测试:

测试输入:ABCDEF###G##H

预期输出: 按先序遍历的结果为:ABDEGCFH 按中序遍历的结果为:DBGEAFHC 按后序遍历的结果为:DGEBHFCA 按层序遍历的遍历结果为:ABCDEFGH 该二叉树的高度为:4


开始你的任务吧,祝你成功!

最后通关代码

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<math.h>

using namespace std;

#define OK  1

#define ERROR 0

#define MAX_TREE_SIZE  100

typedef  char TElemType ;

typedef  TElemType  SqBiTree[MAX_TREE_SIZE];

TElemType Nil='#';

void input(TElemType &x)     // 函数变量

{

    scanf("%c",&x);

}

void visit(TElemType x)  // 函数变量

{

    printf("%c",x);

}

void InitBiTree(SqBiTree &T)

{ // 构造空二叉树T。因为T是数组名,故不需要&

    int i;

    for(i=0;i<MAX_TREE_SIZE;i++)

        T[i]=Nil; // 初值为空(Nil在主程中定义)

}


 

void CreateBiTree(SqBiTree &T)

{ // 按层序次序输入二叉树中结点的值(字符型或整型), 构造顺序存储的二叉树T

    /********** Begin **********/

    int i=1;

    scanf("%s",T+1);

    while(T[i] != '\0')

        i++;

    T[i]='#';

    /********** End **********/

}

int BiTreeEmpty(SqBiTree T)

{ // 初始条件:二叉树T存在。操作结果:若T为空二叉树,则返回TRUE,否则FALSE

    if(T[1]==Nil) // 根结点为空,则树空

        return 1;

    else

        return 0;

}

int BiTreeDepth(SqBiTree T)

{ // 初始条件:二叉树T存在。操作结果:返回T的深度

    /********** Begin **********/

    int i=MAX_TREE_SIZE-1,j;

    while(T[i]=='#')

        i--;

    j=i;

    int dep=0;

    do{

        dep++;

        j=j/2;

    }while(j>=1);

    return dep;

   

    /********** End **********/

}

void PreTraverse(SqBiTree T,int e)

{ // PreOrderTraverse()调用

    /********** Begin **********/

    if(T[e] != '#'){

        visit(T[e]);

        PreTraverse(T,2*e);

        PreTraverse(T,2*e+1);

    }

    /********** End **********/

}

void PreOrderTraverse(SqBiTree T,void(*Visit)(TElemType))

{ // 初始条件:二叉树存在,Visit是对结点操作的应用函数

    // 操作结果:先序遍历T,对每个结点调用函数Visit一次且仅一次

    if(!BiTreeEmpty(T)) // 树不空

        PreTraverse(T,1);

    printf("\n");

}

void InTraverse(SqBiTree T,int e)

{ // InOrderTraverse()调用

    /********** Begin **********/

    if(T[e] != '#'){

        InTraverse(T,2*e);

        visit(T[e]);

        InTraverse(T,2*e+1);

    }  

    /********** End **********/

}

void InOrderTraverse(SqBiTree T,void(*Visit)(TElemType))

{ // 初始条件:二叉树存在,Visit是对结点操作的应用函数

    // 操作结果:中序遍历T,对每个结点调用函数Visit一次且仅一次

    if(!BiTreeEmpty(T)) // 树不空

        InTraverse(T,1);

    printf("\n");

}

void PostTraverse(SqBiTree T,int e)

{ // PostOrderTraverse()调用

    /********** Begin **********/

    if(T[e] != '#'){

        PostTraverse(T,2*e);

        PostTraverse(T,2*e+1);

        visit(T[e]);

    }

    /********** End **********/

}

void PostOrderTraverse(SqBiTree T,void(*Visit)(TElemType))

{ // 初始条件:二叉树T存在,Visit是对结点操作的应用函数

    // 操作结果:后序遍历T,对每个结点调用函数Visit一次且仅一次

    if(!BiTreeEmpty(T)) // 树不空

        PostTraverse(T,1);

    printf("\n");

}

void LevelOrderTraverse(SqBiTree T,void(*Visit)(TElemType))

{ // 层序遍历二叉树

    /********** Begin **********/

    int dep=BiTreeDepth(T);

    int tree_max=pow(dep,2)-1;

    for(int i=1;i<tree_max;i++){

        if(T[i]=='#')

            continue;

        visit(T[i]);

    }

    /********** End **********/

}

int main()

{

    TElemType e;

    SqBiTree Bt;

    InitBiTree(Bt);

    CreateBiTree(Bt);    

    printf("按先序遍历的结果为:");

    PreOrderTraverse(Bt,visit);

    printf("按中序遍历的结果为:");

    InOrderTraverse(Bt,visit);

    printf("按后序遍历的结果为:");

    PostOrderTraverse(Bt,visit);

    printf("按层序遍历的遍历结果为:");

    LevelOrderTraverse(Bt,visit);  

    printf("\n该二叉树的高度为:%d",BiTreeDepth(Bt) );

    return 0;

}

第2关:先序遍历创建二叉链表存储的二叉树及遍历操作

任务描述

本关任务:以二叉链表作存储结构存储二叉树,利用先序递归遍历创建二叉树,并依次进行二叉树的前序、中序、后序递归遍历。

相关知识

在顺序存储结构中,利用数组下标表示元素的位置及元素之间孩子或双亲的关系,因此对于非完全二叉树,如果需要增加很多空结点才能将一棵二叉树改造成为一棵完全二叉树,采用顺序存储结构会造成空间的大量浪费,这时不宜用顺序存储结构, 而应该考虑使用链式存储结构。

二叉树的二叉链表存储结构

由于二叉树的每个结点至多有两个孩子,二叉树链式存储结构的每个结点除了需要存储结点本身的信息之外,还需要存储结点左右孩子的地址,二叉树的链式存储中的结点结构如图所示:

二叉树的链式存储中的结点结构

其中,lchild和rchild是分别指向该结点左孩子和右孩子的指针,data是数据元素的内容,这种结构称为二叉链表。结点的类型声明如下:

 
  1. typedef struct BiTNode
  2. {
  3. TElemType data;
  4. struct BiTNode *lchild, *rchild;
  5. }BiTNode ,*BiTree;
  • data表示数据域,用于存储放入结点值。
  • lchild和rchild分别表示左指针域和右指针域,分别存储左孩子和右孩子结点(即左、右子树的根结点)的存储地址。
  • 当某结点不存在左或右孩子时,其lchild或rchild指针域为NULL。

本实训任务定义data为char型: typedef char TElemType ;

例:画出下列二叉树的二叉链表存储结构 :

二叉树及相应的二叉链表存储结构

二叉树的先序、中序及后序遍历

二叉树的遍历是指按一定的次序访问树中的所有结点,使每个结点恰好被访问一次。其中遍历次序保证了二叉树上每个结点均被访问一次且仅有一次。

二叉树常用的遍历有先序(根)遍历中序(根)遍历后序(根)遍历

因为二叉树的定义就是递归定义,因此采用递归的方法去实现二叉树的三种遍历,所谓先序、中序、后序,区别在于访问根结点的顺序。 1.先序遍历 若二叉树非空,则: ① 访问根结点; ② 先序遍历左子树; ③ 先序遍历右子树。 先序遍历序列的特点:其第一个元素值为二叉树中根结点值。 对应的递归算法实现:

 
  1. void ProOrderTraverse(BiTree T,void(*Visit)(TElemType))
  2. { // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数。
  3. // 先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit
  4. if(T) // T不空
  5. {
  6. Visit(T->data); // 访问根结点
  7. ProOrderTraverse(T->lchild,Visit); // 先序遍历左子树
  8. ProOrderTraverse(T->rchild,Visit); // 先序遍历右子树
  9. }
  10. }

2.中序遍历 若二叉树非空,则: ① 中序遍历左子树; ② 访问根结点; ③ 中序遍历右子树。 中序遍历序列的特点:若已知二叉树的根结点值,以该值为界,将中序遍历序列分为两部分,前半部分为左子树的中序遍历序列,后半部分为右子树的中序遍历序列。

请大家思考中序递归遍历算法与先序递归算法区别与联系。

3.后序遍历 若二叉树非空,则: ① 后序遍历左子树; ② 后序遍历右子树; ③ 访问根结点。 后序遍历序列的特点:最后一个元素值为二叉树中根结点值。

请大家类推后序递归遍历算法。

#字符表示‘无孩子’或指针为空,指针为空时是递归算法的出口。

例:下列二叉树的遍历结果:

二叉树遍历

例:下列二叉树的遍历结果:

前序遍历:ABC##D##EF### 中序遍历:#C#B#D#A#F#E# 后序遍历:##C##DB##F#EA

注意:如果不输出空树,则图1二叉树遍历序列如下:

前序遍历:ABCDEF 中序遍历:CBDAFE 后序遍历:CDBFEA

二叉树最重要的运算是:遍历,解决二叉树的很多问题的方案都是基于对二叉树的遍历。

如何将一颗二叉树以二叉链表形式存入计算机内。

下面以先序递归遍历思想实现二叉树的创建。

先序递归遍历创建二叉树

递归法

在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归

一般地,递归模型由两部分组成,一部分为递归出口,它给出了递归的终止条件。另一部分为递归体,它确定递归求解时的递推关系。

先序递归遍历创建二叉树 利用递归思想,首先创建根结点,再先序创建左子树,再先序创建右子树。

  1. 先创建根,扫描字符序列,读入字符ch,如果ch是一个“#”字符,则表明该二叉树为空树,即T为NULL;否则执行以下操作:
  • 申请一个节点空间T
  • 将ch赋给T->data
  1. 先序递归创建T的左子树;
  2. 先序递归创建T的左子树。

当读入的是一个“#”字符时,是递归的出口。

思考:可否用中序递归遍历或后序递归遍历序列创建二叉树?

编程要求

根据提示,在右侧编辑器补充代码。

  • InitBiTree(BiTree &T)//构造空二叉树T

  • CreateBiTree(BiTree &T)//按先序遍历次序输入二叉树中结点的值(字符型或整型), 构造二叉链表存储的二叉树T

  • PreOrderTraverse(BiTree T,void(*Visit)(TElemType))//先序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • InOrderTraverse(BiTree T,void(*Visit)(TElemType))//中序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • PostOrderTraverse(BiTree T,void(*Visit)(TElemType))//后序遍历二叉树T,对每个结点调用函数Visit一次且仅一次

  • void DestoryBiTree(BiTree &T)// 销毁二叉树

  • #define ClearBiTree DestroyBiTree // 清空二叉树和销毁二叉树的操作一样

测试说明

平台会对你编写的代码进行测试:

测试输入: ABD##FE###CG#H##I##

预期输出: 先序遍历为:A,B,D,F,E,C,G,H,I, 中序遍历为:D,B,E,F,A,G,H,C,I, 后序遍历为:D,E,F,B,H,G,I,C,A,


开始你的任务吧,祝你成功!

最后通关代码

#include <stdio.h>

#include <stdlib.h>

#include "bitree.h"

TElemType Nil='#';

void visit(TElemType s)

{

 printf("%c,",s);

}

void input(TElemType &s)

{  

 scanf("%c",&s);

}

void CreateBiTree(BiTree &T)

{ //按先序次序输入二叉树中结点的值

// 构造二叉链表表示的二叉树T。变量Nil表示空(子)树。

/********** Begin **********/

// if (!T) return ;

TElemType data;

input(data);

if(data == Nil) {

 return ;

}

T = (BiTree)malloc(sizeof(BiTNode));

if(!T) return;

T->data = data;

CreateBiTree(T->lchild);

CreateBiTree(T->rchild);

 /********** End **********/

}

int BiTreeEmpty(BiTree T)

{ // 初始条件:二叉树T存在。操作结果:若T为空二叉树,则返回TRUE,否则FALSE

 /********** Begin **********/

if(!T){

 // if(T->data == Nil) return TRUE;

 // else return FALSE;

 return TRUE;

}

else return FALSE;

/********** End **********/

}

void ProOrderTraverse(BiTree T,void(*Visit)(TElemType))

{ // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数。

// 先序遍历二叉树T的递归算法,对每个数据元素调用函数Visit

 /********** Begin **********/

if (!T) {

 return ;

 }

 Visit(T->data);

  ProOrderTraverse(T->lchild,Visit);

 ProOrderTraverse(T->rchild,Visit);

/********** End **********/

}

void InOrderTraverse(BiTree T,void(*Visit)(TElemType))

{ // 采用二叉链表存储结构,Visit是对数据元素操作的应用函数。

// 中序遍历二叉树T的递归算法,对每个数据元素调用函数Visit

 /********** Begin **********/

 if(!T){

 return;

 }

InOrderTraverse(T->lchild,Visit);

Visit(T->data);

InOrderTraverse(T->rchild,Visit);

/********** End **********/

}

void PostOrderTraverse(BiTree T,void(*Visit)(TElemType))

{ // 初始条件:二叉树T存在,Visit是对结点操作的应用函数

// 操作结果:后序递归遍历T,对每个结点调用函数Visit一次且仅一次

 /********** Begin **********/

 if(!T){

return ;

 }

 PostOrderTraverse(T->lchild,Visit);

 PostOrderTraverse(T->rchild,Visit);

 Visit(T->data);

/********** End **********/

}

void DestoryBiTree(BiTree &T)

{ // 初始条件:二叉树T存在。操作结果:销毁二叉树T

 /********** Begin ?**********/

if(!T){

 return ;

}

DestoryBiTree(T->lchild);

DestoryBiTree(T->rchild);

free(T);

 /********** End **********/

}

第3关:计算二叉树的高度、总节点个数和叶子节点个数

任务描述

本关任务:给定一棵二叉树,计算该二叉树的高度、总节点个数和叶子节点个数。

相关知识

为了完成本关任务,你需要掌握:1.二叉树高度概念,2.二叉树节点,3.二叉树叶子节点概念。

求二叉树高度的递归算法

二叉树高度概念 二叉树的高度指的是二叉树中最大的结点层数。例如:图1所示的二叉树最大的节点层数为3,所以该二叉树高度为3

二叉树高度的递归算法思想

  1. 递归算法设计便是从递归数据结构的基本递归运算入手的。
  2. 对于二叉树,以二叉链为存储结构,其基本递归运算就是求一个结点b的左子树(b->lchild)和右子树(b->rchild)。
  3. b->lchildb->rchild一定是一棵二叉树(这是为什么二叉树的定义中空树也是二叉树的原因)。

一般地,二叉树的递归结构如下:

二叉树递归

  • 对于二叉树b,设f(b)是求解的“大问题”。
  • f(b->lchild)f(b->rchild)为“小问题”。
  • 假设f(b->lchild)f(b->rchild)是可求的,在此基础上得出f(b)f(b->lchild)f(b->rchild)之间的关系,从而得到递归体。
  • 再考虑b=NULL或只有一个结点的特殊情况,从而得到递归出口。

例如,采用二叉链表存储结构,求该二叉树的高度。

算法思想:

  1. 空树的高度为0:如果该节点不存在,返回树高度为0;
  2. 分别递归计算左子树和右子树高度,取左子树和右子树高度中的较大值加1为当前二叉树高度。

即设f(b)为二叉树b的高度: 当b=NULL时,f(b)=0。 当b!=NULL时,f(b->lchild)f(b->rchild)分别为结点b的左、右子树的高度,取f(b->lchild)f(b->rchild)中较大值加1,即为二叉树b的高度。

显然有: f(b)=1 + max( f(b->lchild),f(b->rchild) )

求二叉树总节点数的递归算法

二叉树的节点包含一个数据元素及两个指向子树的分支,例如:图1所示的二叉树的总节点个数为6

例如,采用二叉链表存储结构,求该二叉树的节点的个数。

算法思想:

  1. 空树的节点数为0:如果该节点不存在,返回节点个数为0;
  2. 分别递归计算左子树和右子树的节点数,则当前二叉树的节点数为左、右子树的节点数之和再加1。

即设f(b)为二叉树b的节点数: 当b=NULL时,f(b)=0。 当b!=NULL时,f(b->lchild)f(b->rchild)分别为结点b的左子树的节点数和右子树的节点数,则二叉树b的节点数为f(b->lchild)+f(b->rchild)+1

显然有: f(b)= f(b->lchild)+f(b->rchild)+1

二叉树叶子节点概念

叶子节点是度为0的节点,二叉树节点的度为子树的个数。例如:图1所示的二叉树叶子节点为CDF,个数为3

算法思想:

  1. 空树的叶子节点数为0:如果该节点不存在,返回节点个数为0;
  2. 如果该节点的左子树和右子树同时为空,返回叶子节点个数为1;
  3. 分别递归计算左子树和右子树的叶子节点数,则当前二叉树的节点数为左、右子树的节点数之和。

显然有: f(b)= f(b->lchild)+f(b->rchild)

编程要求

本关的编程任务是补全右侧代码中 BeginEnd中间的代码,具体要求如下:

  • int BiTreeDepth(BiTree T); //计算该二叉树的深度,返回深度值
  • int NodeCount(BiTree T); //计算该二叉树的总的节点个数,返回节点个数
  • int LeafNodeCount(BiTree T); //算该二叉树的叶子节点个数,返回叶子节点个数
测试说明

平台将自动编译补全后的代码,并生成若干组测试数据,接着根据程序的输出判断程序是否正确。

以下是平台的测试样例:

测试输入: ABC##D##EF### 预期输出: 该树的高度为:3 节点的数目为: 6 叶子节点的数目为: 3

测试输入: ABCD###E#F##G## 预期输出: 该树的高度为:4 节点的数目为: 7 叶子节点的数目为: 3


开始你的任务吧,祝你成功!

最后通关代码

#include <stdio.h>

#include <stdlib.h>

#include "bitree.h"

int BiTreeDepth(BiTree T);//计算该二叉树的深度,返回深度值

int NodeCount(BiTree T);//计算该二叉树的总的节点个数,返回节点个数

int LeafNodeCount(BiTree T);//算该二叉树的叶子节点个数,返回叶子节点个数

int main()

{

 BiTree T;

CreateBiTree(T);

printf("该树的高度为:%d\n",BiTreeDepth(T));

printf("节点的数目为: %d\n",NodeCount(T));

 printf("叶子节点的数目为: %d\n",LeafNodeCount(T));

 DestoryBiTree(T);

 return 0;

}

int BiTreeDepth(BiTree T)

{ // 初始条件:二叉树T存在。操作结果:返回T的深度

 /********** Begin **********/

 if(!T) return 0;

 int lchild = BiTreeDepth(T->lchild);

 int rchild = BiTreeDepth(T->rchild);

 return 1+( 1+lchild>1+rchild?lchild:rchild);

// return 1+lchild+rchild;

 /********** End **********/

}


 

int NodeCount(BiTree T)

{

//初始条件:二叉树T存在。操作结果:返回T的结点数

 /********** Begin **********/

 if(!T) return 0;

 int lchild = NodeCount(T->lchild);

 int rchild = NodeCount(T->rchild);

 return 1+lchild+rchild;

 // return 1 + lchild + rchild;

 /********** End **********/

}


 

int LeafNodeCount(BiTree T)

{

 //初始条件:二叉树T存在。操作结果:返回T的叶子结点数

 /********** Begin **********/

 if(!T) return 0;

 if(!T->lchild && !T->rchild) return 1;

 return LeafNodeCount(T->lchild) + LeafNodeCount(T->rchild);

 /********** End **********/

}

第4关:层次遍历二叉树

任务描述

本关任务:给定一棵二叉树,借助队列实现层次遍历二叉树。

相关知识

为了完成本关任务,你需要掌握:1.二叉树层次遍历,2.队列基本操作。

层次遍历二叉树

层次遍历是从根结点出发,按照从上向下,同一层从左向右的次序访问所有的结点。采用层次遍历得到的访问结点序列称为层次遍历序列。

层次遍历序列的特点:其第一个元素值为二叉树中根结点值。

例:图1表示一个二叉树

图1的层次遍历顺序为节点上的数字,结果为:ABECDF

层次遍历二叉树算法思想: 用一个队列保存被访问的当前节点的左右孩子以实现层次遍历。

在进行层次遍历的时候,设置一个队列结构,首先将根节点指针入队列,遍历从二叉树的根节点开始:

  • 从队头取出一个元素,访问该元素所指向的节点;
  • 若该元素所指节点的左右孩子节点非空,则将该元素所指节点的左孩子指针和右孩子指针顺序入队。此过程不断进行,当队列为空时,二叉树的层次遍历结束。
队列基本操作

队列的数据元素类型如下:

 
  1. typedef BiTNode * QElemType;

本关卡提供链队列linkqueue的相关操作和功能,链队列数据类型定义及相关操作函数接口定义如下,在进行二叉树的层次遍历的过程中,你可以根据需要调用以下操作。

 
  1. typedef struct QNode
  2. {
  3. QElemType data;
  4. QNode *next;
  5. }*QueuePtr;
  6. struct LinkQueue
  7. {
  8. QueuePtr front,rear; // 队头、队尾指针
  9. };
  10. void InitQueue(LinkQueue &Q); // 构造一个空队列Q
  11. void DestroyQueue(LinkQueue &Q); // 销毁队列Q,Q不再存在
  12. void ClearQueue(LinkQueue &Q); // 将Q清为空队列
  13. int QueueEmpty(LinkQueue Q); // 若队列Q为空队列,则返回TRUE;否则返回FALSE
  14. int QueueLength(LinkQueue Q); // 返回Q的元素个数,即队列的长度
  15. int GetHead(LinkQueue Q,QElemType &e); // 若队列不空,则用e返回Q的队头元素,并返回OK;否则返回ERROR
  16. int EnQueue(LinkQueue &Q,QElemType e); // 插入元素e为Q的新的队尾元素
  17. int DeQueue(LinkQueue &Q,QElemType &e); // 若队列不空,则删除Q的队头元素,用e返回其值,并返回OK;否则返回ERROR
  18. void QueueTraverse(LinkQueue Q,void(*vi)(QElemType)); // 从队头到队尾依次对队列
编程要求

本关的编程任务是补全右侧代码中BeginEnd中间的代码,具体要求如下:

  •  void LevelOrderTraverse(BiTree T,void(*Visit)(TElemType));//层次遍历二叉树
测试说明

平台将自动编译补全后的代码,并生成若干组测试数据,接着根据程序的输出判断程序是否正确。

以下是平台的测试样例:

测试输入:ABC##D##EF### 预期输出:ABECDF

测试输入:ABCD###E#F##G## 预期输出:ABGCEDF


开始你的任务吧,祝你成功!

最后通关代码

 #include <stdio.h>

#include <stdlib.h>

#include "bitree.h"

#include "linkqueue.h"


 

void LevelOrderTraverse(BiTree T,void(*Visit)(TElemType));


 

int main()

{

    BiTree T;  

    CreateBiTree(T);

    printf("层序遍历为:");

    LevelOrderTraverse(T,visit);

    DestoryBiTree(T);

    return 0;

}


 

void LevelOrderTraverse(BiTree T,void(*Visit)(TElemType))

 { // 初始条件:二叉树T存在,Visit是对结点操作的应用函数

   // 操作结果:层序递归遍历T(利用队列),对每个结点调用函数Visit一次且仅一次

    /********** Begin **********/

LinkQueue q;

QElemType a;

 if(T){

 InitQueue(q);

 EnQueue(q,T);

 while(!QueueEmpty(q)){

 DeQueue(q,a);

 Visit(a->data);

 if(a->lchild)

 EnQueue(q,a->lchild);

 if(a->rchild)

EnQueue(q,a->rchild);

}

 printf("\n");

 }

    /********** End **********/

}

第5关:打印二叉树

任务描述

本关任务:请你实现 PrintTree.cpp 里的void PrintTreeRootLeft(TNode* r, int layer)函数。

相关知识

用二叉树的双指针结构存储二叉树,每个结点所含数据元素均为单个字母,试编程实现按树形状打印二叉树的算法。例如:图 1 的二叉树打印为右边的形状。

图 1 打印树结构示意图

图 1 中的二叉树打印出来的树结构实际上是一个6行4列的矩阵,如图 2 所示。

图 2 树结构打印矩阵

若由下往上收集结点,得到的序列刚好是该树的中序遍历序列,因此打印树结构相当于按照中序遍历序列的相反次序进行打印,每个结点打印一行;该例子中,打印出来的树结点共 4 列,与树的层数相同,每个结点在打印结果中所在的列号等于该结点在树中的层号。

为了便于看清打印结果,我们要求对于打印出的每一行,字母前的每个空格打印为“-----”,即连续5个’-‘;字母打印为形如”----A”的形式;字母后的空格不用打印。这样图 1 的树的打印结果应为:

 
  1. ---------C
  2. -------------------F
  3. --------------E
  4. ----A
  5. --------------D
  6. ---------B

树结点结构定义为:

 
  1. struct TNode{
  2. char data;
  3. struct TNode* left;
  4. struct TNode* right;
  5. };
编程要求

请你实现 PrintTree.cpp 里的void PrintTreeRootLeft(TNode* r, int layer)函数。

 
  1. //PrintTree.cpp
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include "PrintTree.h"
  6. TNode* LayersToTree(char *A, int n)
  7. /*
  8. 功能:由补充虚结点(‘^’)的层序序列构造二叉树
  9. 参数:`A`: 补充了虚结点(‘^’)的层序序列
  10. `n`: 序列的长度
  11. 输出: 所构造的二叉树的根指针,`NULL`表示没有构造出二叉树
  12. */
  13. {
  14. TNode** pnode;
  15. TNode* node;
  16. int i;
  17. if(n<=0)
  18. return NULL;
  19. pnode= new TNode*[n];
  20. for(i=0; i<n; i++){
  21. if(A[i]!='^'){
  22. node=new TNode;
  23. node->data=A[i];
  24. node->left=NULL;
  25. node->right=NULL;
  26. pnode[i]=node;
  27. }
  28. else
  29. pnode[i]=NULL;
  30. }
  31. for(i=0; i<n; i++){
  32. if(pnode[i]==NULL) continue;
  33. if(2*(i+1)<=n && pnode[2*(i+1)-1]!=NULL)
  34. pnode[i]->left=pnode[2*(i+1)-1];
  35. if(2*(i+1)+1<=n && pnode[2*(i+1)]!=NULL)
  36. pnode[i]->right=pnode[2*(i+1)];
  37. }
  38. node=pnode[0];
  39. delete pnode;
  40. return node;
  41. }
  42. void PrintTreeRootLeft(TNode* r, int layer)
  43. //r是树中一棵子树的根,打印以结点r为根的子树,
  44. //layer是r在树中所处的层,约定树的根结点的层号为1
  45. {
  46. //在begin和end之间实现你的代码。
  47. /******begin********/
  48. /******end**********/
  49. }
  50. void DeleteTree(TNode* t)
  51. {
  52. if(t==NULL) return;
  53. if(t->left) DeleteTree(t->left);
  54. if(t->right) DeleteTree(t->right);
  55. delete t;
  56. }
测试说明

本关的测试过程如下:

  1. 平台编译 step2/Main.cpp ;
  2. 平台运行该可执行文件,并以标准输入方式提供测试输入;
  3. 平台获取该可执行文件的输出,然后将其与预期输出对比,如果一致则测试通过;否则测试失败。

输入格式: 输入层序序列

输出格式: 输出打印结果

以下是平台对 step2/Main.cpp 的测试样例:

样例输入: ABD^C^E

样例输出: Print (Root Left): --------------E ---------D ----A --------------C ---------B


开始你的任务吧,祝你成功!

最后通关代码

///

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "PrintTree.h"

/

/*=================================================

函数:TNode* LayersToTree(char *A, int n)

功能:由补充虚结点(‘^’)的层序序列构造二叉树

参数:A: 补充了虚结点(‘^’)的层序序列

? ? ? ? ? n: 序列的长度

输出: 所构造的二叉树的根指针,NULL表示没有构造出二叉树

==================================================*/

TNode* LayersToTree(char *A, int n)

{

 TNode** pnode;

 TNode* node;

 int i;

 if(n<=0)

return NULL;

 pnode= new TNode*[n];

for(i=0; i<n; i++){

 if(A[i]!='^'){

 node=new TNode;

 node->data=A[i];

 node->left=NULL;

 node->right=NULL;

 pnode[i]=node;

 }

else

pnode[i]=NULL;

 }

 for(i=0; i<n; i++){

 if(pnode[i]==NULL) continue;

 if(2*(i+1)<=n && pnode[2*(i+1)-1]!=NULL)

 pnode[i]->left=pnode[2*(i+1)-1];

 if(2*(i+1)+1<=n && pnode[2*(i+1)]!=NULL)

 pnode[i]->right=pnode[2*(i+1)];

}

 node=pnode[0];

 delete pnode;

 return node;

}

void PrintTreeRootLeft(TNode* r, int layer)

//r是树中一棵子树的根,打印以结点r为根的子树,

//layer是r在树中所处的层,约定树的根结点的层号为1

{

 /*请在BEGIN和END之间实现你的代码*/

 /*****BEGIN*****/

if(r->right)

 {

 PrintTreeRootLeft(r->right,layer+1);

 }

printf("----");

 for(int i=1;i<layer;i++)

 printf("-----");

 printf("%c\n",r->data);

 if(r->left)

 {

PrintTreeRootLeft(r->left,layer+1);

}

 /******END******/

 /*请不要修改[BEGIN,END]区域外的代码*/

}

void DeleteTree(TNode* t)

{

 if(t==NULL) return;

 if(t->left) DeleteTree(t->left);

 if(t->right) DeleteTree(t->right);

 delete t;

}





 

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我来用中文回复。本关任务是要求使用队列实现二叉树的层次遍历。具体来说,就是从根节点开始,按照从上到下、从左到右的顺序遍历整棵二叉树,将每个节点的值输出或保存下来。这种遍历方式也被称为广度优先搜索(BFS)。通过使用队列,我们可以按照层次顺序依次将每个节点加入队列中,并在遍历时依次弹出队列中的节点,从而实现层次遍历。 ### 回答2: 二叉树是一种最常见的数据结构之一,其具有简洁、高效的特点,被广泛应用于各种计算和编程领域。在二叉树的遍历过程中,层次遍历是一种非常重要的方法,可以对中的节点按照从上到下、从左到右的顺序进行遍历,并输出每个节点的值。 层次遍历二叉树实现的基本思想是,通过队列来实现节点的操作。具体步骤如下: 1. 将根节点加入队列中。 2. 当队列不为空时,循环执行以下操作: 1)取出队头元素,输出该节点的值。 2)若左子节点不为空,则将左子节点加入队列中。 3)若右子节点不为空,则将右子节点加入队列中。 由于队列具有先进先出的特点,因此可以保证实现层次遍历二叉树时的正确性和完整性。同时,由于队列的容量和使用方式比较灵活,因此层次遍历二叉树方法也具有较强的适应性和可扩展性。 为了提高层次遍历二叉树的效率,还可以结合其他算法数据结构进行优化。例如,在实现过程中可以使用链表等高效的数据结构来替代数组实现队列,从而提高程序的运行效率。同时,还可以采用分层遍历的方式来实现二叉树的遍历,在每一层结束时输出换行符,以便更加便于观察和理解。 总之,通过队列实现层次遍历二叉树是一种简单、直观、高效的方法,可以用来处理各种实际问题,如文件系统、图像处理、网络编程等等。只要掌握了基本的二叉树遍历原理和队列操作,就可以轻松地实现各种功能。 ### 回答3: 二叉树的层次遍历是一种重要的遍历方式,它是按层次从上到下、从左到右的顺序遍历二叉树中所有节点。该遍历方式可借助队列来实现,具体过程如下: 1. 首先,将根节点入队。 2. 然后,循环执行以下操作:从队头取出一个节点,访问该节点;如果该节点有左孩子,则将左孩子入队;如果该节点有右孩子,则将右孩子入队。 3. 直到队列为空,遍历结束。 实现该遍历方式的代码如下: ```python # 定义二叉树节点 class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right # 借助队列实现层次遍历 def levelOrder(root: TreeNode): if root is None: return [] queue = [root] # 初始化队列,根节点入队 res = [] # 存储遍历结果 while queue: size = len(queue) # 记录当前队列长度,即该层节点数 level = [] # 存储该层节点值 for i in range(size): node = queue.pop(0) # 取出队头节点 level.append(node.val) # 记录该节点值 if node.left: # 如果该节点有左孩子,则将左孩子入队 queue.append(node.left) if node.right: # 如果该节点有右孩子,则将右孩子入队 queue.append(node.right) res.append(level) # 将该层节点值存入遍历结果 return res # 测试代码 if __name__ == '__main__': # 构造二叉树 root = TreeNode(3) root.left = TreeNode(9) root.right = TreeNode(20) root.right.left = TreeNode(15) root.right.right = TreeNode(7) # 层次遍历 print(levelOrder(root)) # [[3], [9, 20], [15, 7]] ``` 以上就是借助队列实现二叉树层次遍历的方法和代码实现。该遍历方式的时间复杂度为 O(n),空间复杂度为 O(n)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值