二叉树:二叉树建立的大部分题目(图解所有函数的具体执行过程,各个各个函数的推理过程)

84 篇文章 5 订阅
67 篇文章 2 订阅

问题描述 :
  • 目的:使用C++模板设计并逐步完善二叉树的抽象数据类型(ADT)。

  • 内容:(1)请参照链表的ADT模板,设计二叉树并逐步完善的抽象数据类型。(由于该环境目前仅支持单文件的编译,故将所有内容都集中在一个源文件内。在实际的设计中,推荐将抽象类及对应的派生类分别放在单独的头文件中。参考教材、课件,以及网盘中的链表ADT原型文件,自行设计二叉树的ADT。)

  • 注意:二叉树ADT的基本操作的算法设计很多要用到递归的程序设计方法。

  • (2)基本操作1:二叉树的二叉链表存储形式的建立,完成后将其加入到二叉树的ADT基本操作集中。

  • 要求设计一个递归算法,将二叉树转化为二叉链表的存储形式。

  • 初始条件:definition给出二叉树T的定义(先序序列。无孩子或指针为空的情形,算法通过特殊分隔符识别(输入))。

  • 输出:按definition构造二叉树的二叉链表。

  • 注意:由于测试数据的显示需建立在二叉树的遍历基础上。因此,请在设计好二叉树的三种遍历算法之后(基本操作2),再进行测试。

  • 参考函数代码:

//建立二叉树的存储结构 (外壳部分,用户函数)
template<class ElemType>
void CreateTree(BinaryTree<ElemType> &T, ElemType &str, ElemType &empty){
    ElemType tmp, t[100];
    int num = 0;
    stringstream input_T(str);
    while(input_T >> tmp){
         t[num] = tmp;
         num++;
    }
    BinaryTreeNode<ElemType> *root;
    num = 0;
    root = T.CreateBinaryTree(t, empty, num);
    T.SetRoot(root);
}

//建立二叉树的存储结构 (递归部分,成员函数)
template<class ElemType>
BinaryTreeNode<ElemType>* BinaryTree<ElemType>::CreateBinaryTree(ElemType x[], ElemType &empty, int &n){
        ElemType ch = x[n];
        n++;
        if (ch == empty)
        {
            return NULL;
        }
        else
        {   
            BinaryTreeNode<ElemType> *Node = new BinaryTreeNode<ElemType>;
            Node->data = ch;
            Node->LChild = CreateBinaryTree(x, empty, n);
            Node->RChild = CreateBinaryTree(x, empty, n);
            return Node;
        }
}



二叉树ADT原型参考如下:



/* 二叉表的结点定义 */
template<class ElemType>
struct BinaryTreeNode
{
       ElemType data;
       BinaryTreeNode<ElemType> *LChild, *RChild;
       BinaryTreeNode() : LChild(NULL), RChild(NULL){} //构造函数1,用于构造根结点
       BinaryTreeNode(const ElemType &item, BinaryTreeNode<ElemType> *Lptr = NULL, BinaryTreeNode<ElemType> *Rptr = NULL) //构造函数2,用于构造其他结点  
       //函数参数表中的形参允许有默认值,但是带默认值的参数需要放后面
       {
           LChild = Lptr;
           RChild = Rptr;
           data = item;
       }
      
       ElemType getData(){ return data;}  //取得结点中的数据
       void SetLChild( BinaryTreeNode<ElemType> *link ){ LChild = link; }  //修改结点的左孩子域
       void SetRChild( BinaryTreeNode<ElemType> *link ){ RChild = link; }  //修改结点的右孩子域
       void SetData( ElemType value ){ data = value; }   //修改结点的data域            
       BinaryTreeNode<ElemType> * GetLChild() const{ return LChild;} //获取左孩子结点
       BinaryTreeNode<ElemType> * GetRChild() const{ return RChild;} //获取左孩子结点

};

//二叉树
template<class ElemType>
class BinaryTree{
   private:

      BinaryTreeNode<ElemType> *root;   // 头指针
     
      void BinaryTreeDestroy_Cursive( BinaryTreeNode<ElemType> *T ); //销毁树(递归准备,private)
     
   public:
      //无参数的构造函数
      BinaryTree():root(NULL){}
      //带参数的构造函数
      BinaryTree(const ElemType &item){root = new BinaryTreeNode<ElemType>(item);}
      //生成树
      void makeBinaryTree( const ElemType &item, BinaryTree &left, BinaryTree &right);
      //拷贝构造函数
      //LinkQueue(LinkQueueList<ElemType> &Queue);
      //析构函数
      ~BinaryTree(){BinaryTreeDestroy();}
      //重载函数:赋值
      //LinkList<ElemType>& operator=(LinkList<ElemType> &List);
      //销毁树
      bool BinaryTreeDestroy();
      //销毁子树
      bool ChildTreeDestroy(BinaryTreeNode<ElemType> * root, int flag);
      //清空链表
      //bool LinkQueueClear();
      //返回以元素值x为根结点的(子)二叉树的高度(递归)
      int GetBinaryTreeHeight_Cursive( BinaryTreeNode<ElemType> *T, ElemType &x ) const;
     //统计结点个数
      int BinaryTreeSize( BinaryTreeNode<ElemType> *T ) const;
      //统计度为2的结点个数
      int CountDegreeTwo( BinaryTreeNode<ElemType> *T ) const;
      //判断二叉树是否为空
      bool BinaryTreeisEmpty() const{return root == NULL;}
      //获取根结点元素值
      ElemType GetRootData() const{ return root->data;}
      //给元素值为x的结点重新赋值
      void Assign_NodeData( BinaryTreeNode<ElemType> * root, ElemType &x, ElemType &value );
      //查找值为x的结点的位置(递归)
      void Location_Cursive( BinaryTreeNode<ElemType> * root, const ElemType &x, BinaryTreeNode<ElemType> * &location );
      //查找值为x的结点的孩子结点的指针(flag=0,左孩子;flag=1,右孩子)
      BinaryTreeNode<ElemType> * Location_Child( BinaryTreeNode<ElemType> * parent, int flag ) const;
      //在以元素值x为根结点的位置插入子树(外壳,flag=0,左子树;flag=1,右子树) 
      bool Insert_ChildTree( BinaryTreeNode<ElemType> * parent, BinaryTreeNode<ElemType> * child, int flag );
      //设置根结点
      void SetRoot(BinaryTreeNode<ElemType> * p){ root = p;}
      //获取根结点
      BinaryTreeNode<ElemType> * GetRoot() const{ return root;}
      //获取父结点
      void GetParent_Cursive(BinaryTreeNode<ElemType> * parent, ElemType &x, BinaryTreeNode<ElemType> * &result) const;
      //前序遍历
      bool PreOrderTraverse( BinaryTreeNode<ElemType> *T, bool (*visit)(BinaryTreeNode<ElemType> *T) ) const;  //前序遍历(递归)
      //中序遍历
      bool InOrderTraverse( BinaryTreeNode<ElemType> *T, bool (*visit)(BinaryTreeNode<ElemType> *T) ) const;  //中序遍历(递归)
      //后序遍历
      bool PostOrderTraverse( BinaryTreeNode<ElemType> *T, bool (*visit)(BinaryTreeNode<ElemType> *T) ) const;  //后序遍历(递归)
      //按树状打印出二叉树的形状(递归};
      void Print_BinaryTree( BinaryTreeNode<ElemType> *root, int i ); //按树状打印出二叉树的形状(递归)
      //建立二叉树的存储结构
      BinaryTreeNode<ElemType>* CreateBinaryTree(ElemType x[], ElemType &empty, int &n);
};
输入说明 :

第一行:表示无孩子或指针为空的特殊分隔符

第二行:二叉树的先序序列(结点元素之间以空格分隔)

输出说明 :

第一行:二叉树先序遍历结果

第二行:二叉树中序遍历结果

第三行:二叉树后序遍历结果

输入范例 :
#
A B # C D # # E # # F # G # H # #
输出范例 :
A B C D E F G H 
B D C E A F G H 
D E C B H G F A 
伪码实现(下面这些代码都跑不了,但是可以参考)
计算以某结点为根节点得子二叉树的高度GetBinaryTreeHeight_Cursive
  • 递归整体不好理解,可以尝试着使用栈模拟递归理解一下啊
    在这里插入图片描述

在这里插入图片描述

/*
    描述:通过递归的方式,返回以x根节点的子二叉树的高度
    参数:x为具体的值,
          T为指向以x为具体值的结点的指针
*/
int BinaryTree<ElemType>::GetBinaryTreeHeight_Cursive( BinaryTreeNode<ElemType> *T, ElemType &x ) const{
    int left = 0,right = 0,len = 0;
    if(T)
    {
        left = GetBinaryTreeHeight_Cursive(T->GetLChild(),x);
        right = GetBinaryTreeHeight_Cursive(T->GetRChild(),x);
        if(left >= right)
        {
            len = left + 1;
            return len;
        }
        else
        {
            len = right + 1;
            return len;
        }
    }
    return len;
}
  • 根据上述代码,debug得全过程
    在这里插入图片描述
以递归的方式计算以某个结点为根节点的子树的结点数目BinaryTreeSize
  • 方法跟上面是一样的,如果上面的没有看懂,可以看一下下面的图示
    在这里插入图片描述
/*
    描述:统计以某棵结点为子树得结点得个数
    参数:T为对应的结点
*/
int BinaryTree<ElemType>::BinaryTreeSize( BinaryTreeNode<ElemType> *T ) const
{
    int num = 0 ,numleft = 0,numright = 0;
    if(T)
    {
        numleft = BinaryTreeSize(T->GetLChild());
        numright = BinaryTreeSize(T->GetRChild());
        num = numleft + numright + 1;
        return num;
    }
    return num;
}
计算以某节点为根节点的子树的叶子节点的个数
  • 上述的三种方法基本相同,前两种没有懂也可以看下面这个流程图

在这里插入图片描述

/*
    描述:统计以某棵结点为子的叶子结点得个数
    参数:T为对应的结点
*/
template<class ElemType>
int BinaryTree<ElemType>::CountDegreeZero( BinaryTreeNode<ElemType> *T ) const {
    int num = 0,numleft = 0,numright = 0;

   // cout<<"正在计数"<<endl;

    if(T) {
      //  cout<<"当前的节点为"<<T->getData()<<endl;
      //  cout<<"当前的数目为"<<num<<endl;
        //在判定当前的结点不为空的情况下
        if(!T->GetLChild() && !T->GetRChild()) {
       //     cout<<"度数为2的叶子节点为的"<<T->getData()<<endl;
            num = 1;
        }

        //递归往上进行叶子节点的相加
        else {
            cout<<"非叶子节点为的"<<T->getData()<<endl;
            numleft = CountDegreeZero(T->GetLChild());
            numright = CountDegreeZero(T->GetRChild());
            num = numleft + numright;

        }
    }

    return num;
    //判定是否为叶子结点
}
查找某个结点的特定的位置Location_Cursive
  • 基本得思路和上面几个相似的,只不过条件改成了是否为目标节点
/*
    描述:查找值为x的结点的位置(递归)
    参数:root并不是指向特定节点,只是用来递归的
          x寻找的目标
          location:目标节点的指针
*/
void BinaryTree<ElemType>::Location_Cursive( BinaryTreeNode<ElemType> * root, const ElemType &x
                                            , BinaryTreeNode<ElemType> * &location ){
    
    //判定当前节点是不是目标节点
    if(root && root->getData() == x)
    {
        location = root;
        return;
    }
    else
    {
        //在不是的情况下,进行递归 
        if(root)
        {
            //递归也要不为空
            Location_Cursive(root->GetLChild(),x,location);
            Location_Cursive(root->GetRChild(),x,location);
        }     
    }   
}
返回特定值的结点的父节点GetParent_Cursive
  • 基本方法是一致,大差不差
  • 但是要注意,在调用的对应的子节点的值的时候,需要说明一下,该节点是不为空的
    在这里插入图片描述
/*
    描述:获取父结点
    参数:以x为值得结点父节点为对应的目标节点
          parent为遍历的过程中的迭代量
          result为最终的结果
    
*/
void BinaryTree<ElemType>::GetParent_Cursive(BinaryTreeNode<ElemType> * parent
        , ElemType &x, BinaryTreeNode<ElemType> * &result) const {
    if(parent)
    {
        if((parent->GetLChild()&&parent->GetLChild()->data == x) ||(parent->GetRChild()&&parent->GetRChild()->data == x))
        {
            result = parent;
            return;
        }
        GetParent_Cursive(parent->GetLChild(),x,result);
        GetParent_Cursive(parent->GetRChild(),x,result);
    }
}
三种顺序遍历
  • 下面三种函数中,主要对于参数的visit比较疑惑,知道是一个函数指针,但是就不知道这个函数是干什么的。
  • 结点的数据类型是自己定义的,不是基本数据类型,所以不能直接输出,所以需要写一个专门的输出函数,就是对应的visit
  • 下面是关于函数指针的用法
    在这里插入图片描述
  • 函数指针使用的样例
    在这里插入图片描述
  • 在下述三个方法中,只需要专门声明一下同样返回类型的,同样的参数列表的函数,都可以填入其中的,直接调用。
/*
    描述:前序遍历
    参数:T 整个递归过程中的迭代的量
          (*visit)(BinaryTreeNode<ElemType> *T  函数指针,用于输出访问过的结点
*/
template<class ElemType>
bool BinaryTree<ElemType>::PreOrderTraverse( BinaryTreeNode<ElemType> *T
                                            , bool (*visit)(BinaryTreeNode<ElemType> *T) ) const
{
    if(T)
    {
        visit(T)
        PreOrderTraverse(T.GetLChild(),visit);
        PreOrderTraverse(T.GetRChild(),visit);
    }
}
//前序遍历(递归)

//中序遍历
template<ElemType>
bool BinaryTree<ElemType>::InOrderTraverse( BinaryTreeNode<ElemType> *T
                     , bool (*visit)(BinaryTreeNode<ElemType> *T) ) const 
{
     if(T)
    {
        InOrderTraverse(T.GetLChild(),visit);
        visit(T)
        InOrderTraverse(T.GetRChild(),visit);
    }
}                     
//中序遍历(递归)

//后序遍历
template<ElemType>
bool BinaryTree<ElemType>::PostOrderTraverse( BinaryTreeNode<ElemType> *T
                        , bool (*visit)(BinaryTreeNode<ElemType> *T) ) const

{
    if(T) {
        PostOrderTraverse(T.GetLChild(),visit);
        PostrderTraverse(T.GetRChild(),visit);
        visit(T)
    }
}
//后序遍历(递归)
  • 定义一个visit函数,给遍历过程中的对结点的操作留下很大的空间,可以实现多种的操作,主要是根据输入的函数的进行操作。
按照树的形状打印二叉树Print_BinaryTree
  • 我也很头秃,明明用队列就很好实现的玩意,非得用递归,递归是深度,我是实在不知道怎么用到广度。
  • 为什么不去看看书?经过搜索之后,我看到了答案,我真的裂了。这是哪门子结构?

在这里插入图片描述

  • 通过公共的参数i来传递层数,让每一次遍历知道自己第几层
template<class ElemType>
void BinaryTree<ElemType>::Print_BinaryTree( BinaryTreeNode<ElemType> *root, int i )
{
    int j;
	if (T) {
		Print_BinaryTree(root->GetRChild(), i+1); //访问右子树

		for (j=0; j<i-1; ++j) printf(" ");
		printf("%c\n",	root->getData());

		Print_BinaryTree(root->GetLChild(),i+1); //访问左子树
	}
} //按树状打印出二叉树的形状(递归)
  • 吓得我一慌,我还是赶紧去看看书吧!!!!
根据先序遍历结果生成二叉树方法
  • 这个代码是老师在题目中的已经给出了的,只是画一下思路
    在这里插入图片描述
    在这里插入图片描述
  • 基本上跟前序遍历是一致的,不过访问节点的方式变成了根据扫描的值生成结点。
/*
    描述:建立二叉树的存储结构
    参数:ElemType x[]为存储遍历序列的数组
          empty为遍历序列为某节点为空的标志
          n为用来扫描真个数组的索引
*/

BinaryTreeNode<ElemType>* BinaryTree<ElemType>::CreateBinaryTree(ElemType x[]
        , ElemType &empty, int &n) {
       ElemType ch = x[n];
        n++;
        if (ch == empty)
        {
            return NULL;
        }
        else
        {   
            BinaryTreeNode<ElemType> *Node = new BinaryTreeNode<ElemType>;
            Node->data = ch;
            Node->LChild = CreateBinaryTree(x, empty, n);
            Node->RChild = CreateBinaryTree(x, empty, n);
            return Node;
        }
}


/*
    描述:建立二叉树的存储结构 (外壳部分,用户函数)
    参数:T为二叉树的引用传递
          str为字符串的引用传递
          empty为结束标记
*/
void CreateTree(BinaryTree<ElemType> &T, ElemType &str, ElemType &empty){
    ElemType tmp, t[100];
    int num = 0;
    
    //将对应的字符串使用字符串流转成对应的数组
    stringstream input_T(str);
    while(input_T >> tmp){
         t[num] = tmp;
         num++;
    }

    //定义相关的结点用来承载对应的生成树的根节点
    BinaryTreeNode<ElemType> *root;
    num = 0;
    root = T.CreateBinaryTree(t, empty, num);
    T.SetRoot(root);
}
  • 对于字符串流的学习详见
事故现场
第一次提交

在这里插入图片描述

  • 首先我已经很激动了,因为我写了一整天,终于能写完一题了。
  • 自己先草拟样例
  •    #
       a b d # # e # # c # #
    

*

  • 特殊样例

在这里插入图片描述

  • 错了,那就去验证
    在这里插入图片描述

在这里插入图片描述

  • 原因,是前中后三个顺序的遍历只有外壳是写成对应的顺序,但是内在都是前序遍历,忘记改了
最后一次提交

在这里插入图片描述

整个OJ实现过程的截图
查找特定结点的孩子结点
  • 第一次提交
    在这里插入图片描述
  • 查看错误样例
    在这里插入图片描述
  • 说出来你可能不信,我居然忘记将在本地修改的代码提交到oj上,用错误样例跑了半天
删除某结点的左子树或者右子树
  • 删除一个空间,一定要将指向该空间的指针完全删除
    在这里插入图片描述
  • 删除右节点函数,传进去的仅仅只是对应的指针的赋值,删除一个结点之后,要将一个父节点的后序的指针给定义为空

在这里插入图片描述
在这里插入图片描述

  • 我觉得这样写很不方便,可以传入指针的指针,进行修改。或者直接传入引用也可以
第一次提交

在这里插入图片描述

  • 提交之前改了那么久,居然还会有问题,估计应该是没有检索到对应的数据,没有输出正确得值

在这里插入图片描述

  • 当真是如我所想。主要在没有想到。删除某一个结点的子树,还要确保该子树已经存在的前提下,如果不存在你没有删除,实际上就应该返回false。

在这里插入图片描述

  • 可以使用指针的引用,我们在调用的函数里面修改指针的时候,也会连带着修改外面的主函数的指针的值。如果单纯的是指针的话,就是修改对应的复制的指针了,但是不会修改的原来的主函数中的指针的值
    在这里插入图片描述
获取某节点的孩子节点

在这里插入图片描述

  • 问题在于,如果x本身就是左节点,又让你返回左节点,就是返回为空的

在这里插入图片描述

  • 确实如此
查找双亲结点

在这里插入图片描述

  • 运行超时,说明自拟样例
  • 仅仅只有的根节点
    在这里插入图片描述
    在这里插入图片描述
  • 没有认真审题,添加相关的条件

在这里插入图片描述

  • 除此之外,还有一个问题,那就是如果没有查找到,返回为空,输出的时候没有加上限制,仍旧访问它的结点的值,这不合理。

在这里插入图片描述

分析总结
关于的BinaryTree():root(NULL){}的用法
  • 初始化表达式
BinaryTree():root(NULL){}

等同于

BinaryTree(){root=NULL;}
  • 适用的情况
    • 初始化const成员
    • 初始化引用的成员
    • 当调用基类的构造函数,它拥有一组参数时
    • 调用成员类的构造函数,它拥有一个组参数
关于删除子树的函数
  • 如下,递归准备就是递归的具体的内容,就是递归的过程,不是外壳函数
void BinaryTreeDestroy_Cursive( BinaryTreeNode<ElemType> *&T ); //销毁树(递归准备,private)
  • 如下为删除子树的函数,是删除某个结点的左子树或者右子树,当flag=0为删除左子树,当flag=1,删除右子树。
bool ChildTreeDestroy(BinaryTreeNode<ElemType> * root, int flag);
关于计算以特定值为根节点的子二叉树的高度GetBinaryTreeHeight_Cursive
  • T为指向以x为值得指针
template<class ElemType>
int BinaryTree<ElemType>::GetBinaryTreeHeight_Cursive( BinaryTreeNode<ElemType> *T, ElemType &x ) const{
    
}
  • T就是指向一个x为值的结点的指针。

  • 下述为个人的胡思乱想

  • 如果这是一个递归调用,那么递归迭代的部分是什么?是T?按理来说应该是这样的,一开始的值是root,但这又面临一个问题,我开始在这个函数体中就写入了赋值,那么后面的递归调用都将使用这个值,会陷入死循环。

  • 解决办法只有将这个函数当作一个外壳函数。

  • 上述方法都是胡乱思索,参考了一下别人的,发现一个问题的,那就是参数说明给的不明不白,还要别人胡乱猜测。

  • 为什么会想那么多,因为自己不熟悉这些套路,不够熟练,这些似乎都约定俗成了,我并不了解。

针对题目给的ADT的一些想法
  • 题目如果后面加了一个recursive,那就说明你要是用的递归
  • 不特别说明的情况下,如果给你一个指针和一个值,那这个指针就是指向以这个值为值的结点的。不必再纠结到底是不是,不然太累了。
关于递归的几种个人总结的技巧
  • 总共是两个方向,生成递归是一个方向,结束递归获得返回值又是一个方向
  • 去的时候,没有深层递归的返回值;来时,已经拥有了相关的值
  • 递归之间相互影响,可以通过两种方式
    • 以递归函数的返回值为连接多层递归的链路。
    • 以贯穿整个的整个过程不变的引用参数为链路,串接起整个过程
如果有疑问或者不妥,请加扣扣651378276,一起商讨进步。
  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值