一、实验目的和要求
目的:设计并实现基于后序线索二叉树的后序遍历的非递归算法。
要求:
(1)创建二叉树。
(2)转换为后序线索二叉树。
(3)实现后序遍历的非递归算法。
(4)其它要求同课后作业-01要求。
二、实验环境
软件环境:visual stdio 2017
硬件环境:①CPU:Intel(R)Core(TM)i7-8565U CPU @1.80Ghz
②内存:8.0GB
三、实验内容
设计并实现基于后序线索二叉树的后序遍历的非递归算法。
四、实验过程
4.1 任务定义和问题分析
本次实验主要解决的任务可以分解为:建立一颗二叉树,将二叉树进行后序线索化,对线索化后的二叉树进行后序遍历。
4.2 数据结构的选择和概要设计
数据结构采取链式结构建立树,每个结点增加部分辅助空间以服务于二叉树的线索化。
通过编写后序线索化函数将树中叶子结点空闲的左儿子指针和右儿子指针指向逻辑上的前驱后继。
4.3 详细设计
每个二叉树结点新增1个指针,2个bool型标记。指针为父亲指针,指向结点的父亲。两个bool型标记表示该节点的左/右儿子指针是否指向逻辑上的前驱后继还是子节点。
template<typename T>
struct BTNode
{
T data;
bool ltag;
bool rtag;
BTNode<T>* leftson;
BTNode<T>* rightson;
BTNode<T>* father;
};
将二叉树封装为类:
template<typename T>
class BT
{
public:
BT();
~BT();
void build(); //建树
void erase(); //删除
void ToPoClueBT(); //转换为后序线索二叉树
void PoTraversal(); //后序线索遍历
protected:
void build(BTNode<T>* & father, BTNode<T>* & root);
void erasebyRecursion(BTNode<T>* & root);
void ToPoClueBTAssist(BTNode<T>*&root, string& str); //转换函数的辅助函数
private:
BTNode<T>* root;
bool isclued; //标记是否被线索化
bool isbuilt; //标记是否已建树
};
建树采取输入扩展先序序列递归建树的方法。
template<typename T>
void BT<T>::build()
{
if (isbuilt)
{
cout << "The binary Tree has been built , if you want to rebuild , please erase it ! " << endl;
return;
}
this->isbuilt = true;
BTNode<T>* null = NULL;
build(null, this->root);
}
template<typename T>
void BT<T>::build(BTNode<T>* & father,BTNode<T>* & root)
{
char ch = cin.get();
if (ch == '.')
{
root = NULL;
return;
}
root = new BTNode<T>;
root->father = father;
root->data = ch;
root->ltag = false;
root->rtag = false;
build(root, root->leftson);
build(root, root->rightson);
}
对于二叉树的后序线索化,采用后序递归遍历的方法找到子节点为空的结点,将其空指针指向前驱/后继。有以下几种情况:
①左孩子结点为空:
- 若结点有右孩子,则该节点的左孩子指针指向其右孩子,同时标记值ltag更新为true.
- 若结点无右孩子,且该结点的父结点有左孩子且不为自己,则该结点的左孩子指针指向其父节点的左孩子,同时标记值ltag更新为true.
- 若结点无右孩子,且该结点的父节点无左孩子,则再继续探寻父节点的父节点,探寻方式与判断条件与前述相同(我是利用了一个string值随着作为函数参数传入判断是当前结点是父节点的左孩子还是右孩子),直到所探寻的父节点有左孩子结束探寻。该结点的左孩子指针指向探寻到的父节点的左孩子,同时标记值ltag更新为true.
- 【期末复习的时候发现之前写的这篇博客在解析上存在疏漏,但是代码还是真确的】 缺少的情况为结点无右孩子且是父节点的左儿子。在这种情况下,迭代(用词可能不正确,就是不断用父节点的父节点来更新“父节点”)父节点直至某个“父节点”是其父节点的右儿子且这个“父节点”的父节点的左儿子存在,则结点的前驱为这个“父节点的左儿子”。【这种情况在代码上可以和前两种合并,具体体现详见后述代码】
- 不满足上述者,则该结点为整棵树的后序线索的最前的前驱。
②右孩子结点为空: - 若结点为其父节点的右孩子,则该结点右孩子指针指向其父节点,同时标记值rtag更新为true
- 若结点为其父节点的左孩子,且该结点的父节点无右孩子,则该结点右孩子指针指向其父节点,同时标记值rtag更新为true
- 若结点为其父节点的左孩子,且该结点的父节点有右孩子,则该结点右孩子指针指向其父节点的右子树的最左的左叶子结点。
template<typename T>
void BT<T>::ToPoClueBT()
{
if (isclued)
{
cout << "The binary Tree has been clued , your instruction is to be canceled ! " << endl;
return;
}
this->isclued = true;
string empty = "";
ToPoClueBTAssist(this->root, empty);
}
template<typename T>
void BT<T>::ToPoClueBTAssist(BTNode<T>* & root, string& str)
{
if (root->leftson != NULL) ToPoClueBTAssist(root->leftson, str += '0');
if (root->rightson != NULL) ToPoClueBTAssist(root->rightson, str += '1');
if (root->leftson == NULL)
{
if (root->rightson != NULL)
{
root->ltag = true;
root->leftson = root->rightson;
}
else
{
BTNode<T>* node = root;
for (int i = str.size() - 1; i > 0; i--)
{
if (str[i] == '1')
{
root->ltag = true;
if (node->father->leftson != NULL)
{
root->leftson = node->father->leftson;
break;
}
}
node = node->father;
}
}
}
if (root->rightson == NULL)
{
root->rtag = true;
if (str[str.size() - 1] = '1') root->rightson = root->father;
else
{
BTNode<T>* Node = root->father->rightson;
if (Node == NULL) root->rightson = root->father;
else
{
while (Node->leftson != NULL) { Node = Node->leftson; }
root->rightson = Node;
}
}
}
}
对于后序线索二叉树的后序遍历,初始化node=根结点,建立循环,用字符串记录各结点data值,每次记录后便将node更新为前驱,更新的时候利用ltag与rtag进行判断逻辑上的前驱的位置。最后将字符串反向输出。
template<typename T>
void BT<T>::PoTraversal()
{
string OppositeResult, Result;
BTNode<T>* node = this->root;
while (node != NULL)
{
OppositeResult += node->data;
if (node->ltag) node = node->leftson;
else
{
if (node->rightson != NULL && !node->rtag) node = node->rightson;
else node = node->leftson;
}
}
for (int i = OppositeResult.size() - 1; i >= 0; i--) Result += OppositeResult[i];
cout << "The result of postorder clue binary tree traversal is:" << Result << endl;
}
五、测试及结果分析
5.1 实验数据
测试如图所示的二叉树
5.2 结果及分析
结果正确 ,且在写算法时各种测试的数据皆能正确输出。
六、实验收获
通过本次实验,我掌握如何将二叉树线索化的方法,同时本次实验也加深了我对数据结构的理解,自己的编码水平也得到了一定的提升。
七、源代码
#include<iostream>
#include<string>
using namespace std;
template<typename T>
struct BTNode
{
T data;
bool ltag;
bool rtag;
BTNode<T>* leftson;
BTNode<T>* rightson;
BTNode<T>* father;
};
template<typename T>
class BT
{
public:
BT();
~BT();
void build(); //建树
void erase(); //删除
void ToPoClueBT(); //转换为后序线索二叉树
void PoTraversal(); //后序线索遍历
protected:
void build(BTNode<T>* & father, BTNode<T>* & root);
void erasebyRecursion(BTNode<T>* & root);
void ToPoClueBTAssist(BTNode<T>*&root, string& str); //转换函数的辅助函数
private:
BTNode<T>* root;
bool isclued; //标记是否被线索化
bool isbuilt; //标记是否已建树
};
template<typename T>
BT<T>::BT()
{
root = new BTNode<T>;
isclued = false;
isbuilt = false;
}
template<typename T>
BT<T>::~BT()
{
this->erase();
}
\
template<typename T>
void BT<T>::erase()
{
if (isbuilt)
{
BTNode<T>* node = new BTNode<T>;
while (root != NULL)
{
node = root;
if (root->ltag) root = root->leftson;
else
{
if (root->rightson != NULL && !node->rtag) root = root->rightson;
else root = root->leftson;
}
delete node;
}
}
else this->erasebyRecursion(this->root);
isbuilt = 0;
isclued = 0;
root = NULL;
}
template<typename T>
void BT<T>::erasebyRecursion(BTNode<T>* &node)
{
if (node == NULL) return;
erasebyRecursion(node->leftson);
erasebyRecursion(node->rightson);
delete node;
}
template<typename T>
void BT<T>::build()
{
if (isbuilt)
{
cout << "The binary Tree has been built , if you want to rebuild , please erase it ! " << endl;
return;
}
this->isbuilt = true;
BTNode<T>* null = NULL;
build(null, this->root);
}
template<typename T>
void BT<T>::build(BTNode<T>* & father,BTNode<T>* & root)
{
char ch = cin.get();
if (ch == '.')
{
root = NULL;
return;
}
root = new BTNode<T>;
root->father = father;
root->data = ch;
root->ltag = false;
root->rtag = false;
build(root, root->leftson);
build(root, root->rightson);
}
template<typename T>
void BT<T>::ToPoClueBTAssist(BTNode<T>* & root, string& str)
{
if (root->leftson != NULL) ToPoClueBTAssist(root->leftson, str += '0');
if (root->rightson != NULL) ToPoClueBTAssist(root->rightson, str += '1');
if (root->leftson == NULL)
{
if (root->rightson != NULL)
{
root->ltag = true;
root->leftson = root->rightson;
}
else
{
BTNode<T>* node = root;
for (int i = str.size() - 1; i > 0; i--)
{
if (str[i] == '1')
{
root->ltag = true;
if (node->father->leftson != NULL)
{
root->leftson = node->father->leftson;
break;
}
}
node = node->father;
}
}
}
if (root->rightson == NULL)
{
root->rtag = true;
if (str[str.size() - 1] = '1') root->rightson = root->father;
else
{
BTNode<T>* Node = root->father->rightson;
if (Node == NULL) root->rightson = root->father;
else
{
while (Node->leftson != NULL) { Node = Node->leftson; }
root->rightson = Node;
}
}
}
}
template<typename T>
void BT<T>::ToPoClueBT()
{
if (isclued)
{
cout << "The binary Tree has been clued , your instruction is to be canceled ! " << endl;
return;
}
this->isclued = true;
string empty = "";
ToPoClueBTAssist(this->root, empty);
}
template<typename T>
void BT<T>::PoTraversal()
{
string OppositeResult, Result;
BTNode<T>* node = this->root;
while (node != NULL)
{
OppositeResult += node->data;
if (node->ltag) node = node->leftson;
else
{
if (node->rightson != NULL && !node->rtag) node = node->rightson;
else node = node->leftson;
}
}
for (int i = OppositeResult.size() - 1; i >= 0; i--) Result += OppositeResult[i];
cout << "The result of postorder clue binary tree traversal is:" << Result << endl;
}
int main()
{
BT<char> tree;
cout << "Please enter the expanded tree preorder list :";
tree.build();
tree.ToPoClueBT();
tree.PoTraversal();
return 0;
}