数据结构课程实验四:二叉树线索化相关操作

实验日期:2022-12-05

目录

一、实验目的

二、实验内容

三、程序设计

(一)概要设计

(二)详细设计

1.相关结构体、全局变量和数据类型的定义

2.函数的功能设计及函数设计的思路与意图 

(三)程序文件结构

四、程序实现及程序流程图

1.程序代码实现

 2.程序流程图

五、系统测试与程序亮点

1.系统测试截图

2.程序亮点

六、实验思考与体会

八、实验总结


一、实验目的

1、掌握二叉树线索化的建立

2、掌握二叉树线索化的遍历

3、掌握二叉树线索化的销毁

二、实验内容

具体请完成如下功能:

  1. 设计算法实现对二叉树的链式存储;
  2. 中序遍历二叉树;
  3. 销毁二叉树;

三、程序设计


概要设计

首先定义一个线索二叉树其采用链式存储二叉树结构体内不仅定义左右子树指针还定义左右标志变量在无左/右孩子时指针指向前驱/后驱然后进行二叉树的创建通过先序次序输入二叉树各节点的值若为空则输入‘#创建成功后程序通过一个while循环和一个switch语句实现了功能的多次选择执行使用户可进行功能选择具体功能有进行二叉树的中序线索化遍历中序线索二叉树销毁二叉树和退出功能选择用户测试完所有功能后可退出程序

(二)详细设计

1.相关结构体全局变量和数据类型的定义

(1)采用枚举类型,定义PointTag数据类型由此定义左右标志变量ltagrtag

 

(2)定义线索二叉树结构体

 

采取链式存储结构体的成员变量有左右孩子指针左右标志变量数据域

(3)关键全局变量pre的定义

为了记下遍历过程中访问节点的先后关系定义指针pre始终指向刚刚访问过的节点而指针p在代码中出现指向当前访问的节点由此记录下遍历过程中访问节点的先后关系

2.函数的功能设计及函数设计的思路与意图


 (1)void CreateBiTree(BiTree* T)

I功能创建一个二叉树并实现初始化

II设计思路和意图设计此函数主要是为了实现二叉树的创建具体的过程首先按先序次序输入二叉树每个节点的值从根节点开始逐个输入),若节点为空则输入‘#非空则申请内存空间存储数据然后通过递归继续创建当前节点的左子树和右子树若输入的序列符合先序次序则函数运行成功程序进行功能选择阶段此步骤是实现后续线索化中序二叉树和遍历中序二叉树的基础

(2)void InThreading(BiTree T)

I 功能对树中以任意节点T为根的二叉树进行中序线索化

II设计思路和意图设计此函数主要是在下一个函数“带头节点的二叉树中序线索化”中进行调用将此功能进行提取定义为一个函数提高了代码的封装性

思路在根节点非空的情况下首先进行左子树的递归线索化若其左孩子为空则给根节点T加上标志并将左指针指向前驱pre)。然后对根节点前驱pre的右孩子是否为空进行判断若为空则将其右指针指向根节点T若非空则加上标志Linked此时保持pre指向T然后对根节点T的右子树进行递归线索化最后实现整个树结构的线索化

(3)void InOrderThreading(BiTree* Thrt, BiTree T)

I  功能将带头节点的二叉树进行中序线索化

II 设计思路和意图

设计此函数主要通过对函数void InThreading(BiTree T)的调用实现对整个二叉树的中序线索化

思路首先对树非空的情况进行判断在树非空的情况下创建并初始化头节点将其左指针指向根节点T左孩子),右指针指向自己指针pre指向头节点然后对以T为根节点的二叉树进行中序线索化线索化结束后pre为最右节点将其进行线索化即将pre的右线索指向头节点而头节点的右线索也指向pre由此实现整个二叉树的中序线索化

    (4)void InOrderTraverse(BiTree Thrt)

   I 功能遍历中序线索二叉树非递归算法

II 设计思路和意图设计此函数用于对中序线索二叉树的遍历具体实现是在控制台输出二叉树各个节点的数据

思路首先定义节点p为头节点的左孩子即根节点然后进行循环至p等于头节点时循环结束在p不等于头节点时沿左孩子向下访问其左子树为空的节点而后沿右线索访问后继节点左子树访问结束后转向p的右子树重复以上操作对每个数据元素直接输出

(5)void Destroy(BiTree* Thrt)

I 功能实现二叉树的销毁操作释放内存空间

II 设计思路和意图: 为了达到实验要求“销毁二叉树”同时此步骤也是本次实验不可少的一步因为使用了malloc函数申请了空间后为了避免内存泄漏就要使用free函数释放空间释放后并把指针置NULL

思路首先定义指针p指向根节点进行二叉树的遍历遍历中首先找到中序遍历的第一个节点然后定义指针adjust指向p指向的节点而p指向直接后继即根结点同时释放adjust节点的内存空间执行完上述操作后更新adjust指针p指针指向其右孩子并释放掉adjust节点的内存空间最后释放头节点并给野指针置NULL完成二叉树的销毁

(6)主函数int main(void) 

I 功能为用户提供多次功能选择并进行程序功能检测

II 设计思路和意图为了验证所设计的程序是否具备实验要求的所有功能设计此函数供用户检测

思路首先定义头节点和二叉树并在功能选择前完成二叉树进行创建和初始化操作然后进行功能选择用户可选择对二叉树进行中序线索化遍历中序二叉树销毁二叉树和退出程序等操作借助与while和switch语句用户可在执行退出程序操作前选择多种进行测试大大减少了测试时长

(三)程序文件结构

  1. 头文件BinTreeThread.h

内部存放系统头文件结构体的定义枚举类型的定义及所涉及函数的声明

  1. 功能文件BinTreeThread.cpp

调用BinTreeThread.h及系统头文件内部存放所有功能函数的具体实现

  1. 主函数文件main.cpp

调用BinTreeThread.h及系统头文件实现主函数

四、程序实现及程序流程图

1.程序代码实现

//  BinTreeTread.h
//  二叉树
//
//  Created by peach on 2022/12/5.
//
#ifndef BinTreeTread_h
#define BinTreeTread_h
#include 
#define _CRT_SECURE_NO_WARNINGS
#include 
//定义一个线索二叉树
//此处采用枚举类型,定义数据类型PointTag链式存储
typedef enum { Linked, Thread } PointTag;
typedef struct Node {
    char data;
    /*左右孩子指针*/
    struct Node* lchild;
    struct Node* rchild;
    /*左右标志,规定为Linked时存在左/右孩子*/
    PointTag ltag;
    PointTag rtag;
}BitNode, *BiTree;
//函数声明
void CreateBiTree(BiTree* T);
void InThreading(BiTree T) ;
void InOrderThreading(BiTree* Thrt, BiTree T) ;
void InOrderTraverse(BiTree Thrt);
void Destroy(BiTree* Thrt);
#endif /* BinTreeTread_h */
//  BinTreeTread.cpp
//  二叉树
//
//  Created by peach on 2022/12/5.
//
#include "BinTreeTread.h"
#include 
//创建一个二叉树
void CreateBiTree(BiTree* T) {
    char ch = 0;
    /*按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树T*/
    scanf("%c", &ch);
    if (ch == '#') {
        *T = NULL;
    } else {
        *T = (BiTree)malloc(sizeof(BitNode));
        if (!(*T)) {
            //若申请内存失败,则退出程序
            exit(-1);
        }
        //输入的数据作为二叉树根节点数据域
        (*T)->data = ch;
        CreateBiTree(&((*T)->lchild));
        CreateBiTree(&((*T)->rchild));
    }
}



//定义变量pre,始终指向已经线索化的结点
BiTree pre;
//中序线索二叉树
void InThreading(BiTree T) {
    //以T为根节点
    if (T) {
        //左子树递归线索化
        InThreading(T->lchild);
        //p的左孩子为空
        if (!T->lchild) {
            //给T加上左线索
            T->ltag = Thread;
            //T的左孩子指针指向pre(前驱)
            T->lchild = pre;
        }
        else {
            T->ltag = Linked;
        }
        //pre右孩子为空
        if (!pre->rchild) {
            //给pre加上右线索
            pre->rtag = Thread;
            //pre的右孩子指针指向T(后继)
            pre->rchild = T;
        } else {
            pre->rtag = Linked;
        }
        //保持pre指向T
        pre = T;
        //右子树递归线索化
        InThreading(T->rchild);
    }
}

//带头节点的二叉树中序线索化
void InOrderThreading(BiTree* Thrt, BiTree T) {
    //排除树非空情况
    if (!T) {
        return;
    }
    //创建头结点
    *Thrt = (BiTree)malloc(sizeof(BitNode));
    if (!(*Thrt)) {
        exit(-1);
    }
    //将头结点初始化
    //头节点有左孩子
    (*Thrt)->ltag = Linked;
    //头节点的右孩子指针为右线索
    (*Thrt)->rtag = Thread;
    //头节点的左孩子指向根
    (*Thrt)->lchild = T;
    //初始化时右指针指向自己
    (*Thrt)->rchild = *Thrt;
    //pre始终指向已经线索化的结点
    pre = *Thrt;
    //以T为根的二叉树进行中序线索化
    InThreading(T);
    //线索化结束后,pre为最右节点,将最后一个结点pre线索化,即pre的右线索指向头节点
    pre->rtag = Thread;
    pre->rchild = *Thrt;
    //头节点的右线索指向pre
    (*Thrt)->rchild = pre;
}

//中序遍历线索化二叉树
void InOrderTraverse(BiTree Thrt) {
    //指向根结点
    BiTree p = Thrt->lchild;
    //p == Thrt 为循环一遍
    while (p != Thrt) {
        //找到每个子树的第一个结点
        while (p->ltag == Linked) {
            p = p->lchild;
        }
        //访问左子树为空的节点
        printf("%2c", p->data);
        //依次遍历直接后继, 直到转移到右子树或者遍历结束
        while (p->rtag == Thread && p->rchild != Thrt) {
            p = p->rchild;
            //沿右线索访问后继节点
            printf("%2c", p->data);
        }
        //转移到右子树
        p = p->rchild;
    }
    printf("\n");
}


//销毁线索化二叉树
void Destroy(BiTree* Thrt) {
    //p 指向根结点
    BiTree p = (*Thrt)->lchild;
    BiTree adjust = p;
    //p 指向头结点代表遍历结束
    while (p != *Thrt) {
        //找到中序遍历的第一个结点
        while (p->ltag == Linked) {
            p = p->lchild;
        }
        //调整指针adjust指向p指向的结点
        //p指向直接后继(即子树的根结点)
        //释放掉adjust结点(左孩子)的内存空间
        while (p->rtag == Thread && p->rchild != *Thrt) {
            adjust = p;
            p = p->rchild;
            free(adjust);
        }
        //更新adjust
        adjust = p;
        //p最后再指向右孩子
        p = p->rchild;
        //释放掉adjust(子树根结点)的内存空间
        free(adjust);
    }
    //最后释放头结点
    free(*Thrt);
    //给野指针赋值
    *Thrt = NULL;
    adjust = p = NULL;
}

 

//  main.cpp
//  二叉树
//
//  Created by peach on 2022/12/5.
//

#include 
#include "BinTreeTread.h"
#include 
int main(void) {
    //定义二叉树和头节点
    BiTree T1, Thrt = nullptr;
    //定义变量choice,决定是否进行二叉树的销毁
    int choice;
    //创建并初始化二叉树
    printf("在执行功能选择前请按先序次序输入二叉树中结点的值(一个字符),创建二叉链表表示的二叉树:\n");
    printf("提示:若输入的序列不符合先序次序,程序无法进行功能选择界面!\n");
    CreateBiTree(&T1);
    while(1){
    printf("-------------------------------------------------------\n");
    printf("---------------------功能选择界面------------------------\n");
    printf("1--->进行二叉树中序线索化\n");
    printf("--功能2提示:在执行此功能之前,请执行功能1完成二叉树的中序线索化!--\n");
    printf("2--->遍历中序线索二叉树\n");
    printf("--功能3提示:销毁二叉树操作请在二叉树非空情况下进行!-------------\n");
    printf("3--->进行二叉树的销毁\n");
    printf("4--->退出功能选择!\n");
    printf("请输入您的功能选择:\n");
    scanf("%d",&choice);
    switch(choice){
        case 1:
    //进行二叉树中序线索化
    InOrderThreading(&Thrt, T1);
    printf("二叉树中序线索化成功!\n");
            break;
        case 2:
    //遍历中序线索二叉树
    printf("遍历中序线索二叉树:\n");
    InOrderTraverse(Thrt);
            break;
        case 3:
            //确保二叉树操作在二叉树非空情况下进行
            if(Thrt){
    Destroy(&Thrt);
            }
            else{
                printf("二叉树为空!不可进行销毁!\n");
            }
    if (!Thrt) {
        printf("二叉树销毁成功!\n");
    }
            break;
        case 4:
            printf("祝您愉快,欢迎下次使用!\n");
            exit(0);
        default:printf("请输入1~4实现功能选择!!!\n");
}
    }
    return 0;
}

 2.程序流程图

 3.线索二叉树简图

五、系统测试与程序亮点

1.系统测试截图

(1)二叉树创建及初始化、二叉树中序线索化

(2)功能二遍历中序线索二叉树

 (3)功能三销毁二叉树

 

(4)结束程序运行 

 

2.程序亮点

(1)枚举类型的使用

typedef enum {Linked,Thread},使用此方法可代替一成不变的int类型标志使程序更加规范化更具有高级感

(2)递归算法的多次使用

由于二叉树本身就属于递归的数据结构递归算法在本程序中出现多次在函数void CreateBiTree(BiTree* T)为了初始化二叉树使用递归对每个节点进行赋值从根节点开始然后在左子树和右子树上进行递归如下

//输入的数据作为二叉树根节点数据域

        (*T)->data = ch;

        CreateBiTree(&((*T)->lchild));

        CreateBiTree(&((*T)->rchild));

此外在函数void InThreading(BiTree T)同样使用递归完成了根和其左子树及右子树的线索化总之递归算法的使用使程序更加简洁可读性大大提高

(3)main函数中的功能选择菜单设计

核心是采用一个while循环和一个switch语句将功能选择的输入语句和switch语句全部放入循环待程序执行完一种功能后进行下一次循环即功能选择直到用户选择其中choice==4即退出程序整个运行才算全部结束此结构借鉴了曾经设计过的学生成绩管理系统的菜单结构总体结构比较清晰程序功能可进行多次选择可一次性测试此多维数组的各种功能省去了多次执行程序和输入数据的时间在此次实验中数据的输入放在了功能选择前执行并在功能选择菜单中添加了功能提示大大提高了程序可操作性

 

(4)程序健壮性的体现 

由于初始化二叉树中输入的数据为字符类型因此程序本身健壮性很高

本程序以两处为例体现健壮性的提高

I  switch语句中的default语句在输入的数字不属于1~4若不采用default语句程序不会作出任何提示大大降低了程序健壮性和用户体验感

II main函数中功能三“二叉树销毁”中添加对二叉树是否为空的判断语句后可以很好的避免二叉树为空但被迫执行销毁操作导致报错的情况大大提高了程序健壮性

六、实验思考与体会

 

  1. 本次实验采用链式存储的方式进行二叉树的存储利用二叉链表中的n+1个空指针域来存放指向某种遍历次序下的前驱节点和后继节点的指针实现二叉树的线索化
  2. 在做本次实验之前我对二叉树的理解并不算深甚至不理解为什么要进行二叉树的线索化在进行一定的思考后我认为引用二叉线索树的目的是获取节点在任一序列的前驱和后继的信息并加快节点前驱和后继的速度
  3. 本次实验的要求和目的都十分清晰前两个目标即实现链式存储和遍历中序线索化二叉树很快就编写了出来花了很多时间在“销毁二叉树”的代码编写上思考了很久“如何销毁”怎样才算真正的销毁”为什么要销毁”在我看来释放头节点后对野指针赋值置NULL是很关键的一步
  4. 通过此次实验我对树的存储结构二叉树的存储表示也有了更深的理解和区分
  1. 二叉树的存储表示分为顺序存储和链式存储前者更适合完全二叉树由于很多情况下会造成存储空间的极大浪费因而后者是二叉树更为常用的存储结构
  2. 树的存储结构有三种双亲表示法孩子表示法和孩子兄弟表示法任何一棵树都能通过孩子兄弟表示法转换为二叉树进行存储

八、实验总结

通过对二叉树线索化相关操作代码的编写我较好的掌握了二叉树线索化的建立遍历和销毁操作除此之外我还掌握了如何寻找节点的前驱和后继的方法对二叉树有了更深的理解

同时二叉树是一类具有层次关系的非线性数据结构但其本身就是一个递归的数据结构因此递归思想在此次编写程序的过程中得到多方面运用

分享结束,感谢观看!!!

你可能感兴趣的:(实验报告,数据结构,C++/C语言,数据结构,算法,霍夫曼树,c++,随机森林)