文章目录
问题描述 :
-
目的:使用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,那就说明你要是用的递归
- 不特别说明的情况下,如果给你一个指针和一个值,那这个指针就是指向以这个值为值的结点的。不必再纠结到底是不是,不然太累了。
关于递归的几种个人总结的技巧
- 总共是两个方向,生成递归是一个方向,结束递归获得返回值又是一个方向
- 去的时候,没有深层递归的返回值;来时,已经拥有了相关的值
- 递归之间相互影响,可以通过两种方式
- 以递归函数的返回值为连接多层递归的链路。
- 以贯穿整个的整个过程不变的引用参数为链路,串接起整个过程