文章目录
介绍:
代码使用如下二叉树作为样例,其括号表示法表示为:A(B(D(,G)),C(E,F)).
前序遍历结果:A B D G C E F
中序遍历结果:D G B A E C F
后序遍历结果:G D B E F C A
层次遍历结果:A B C D E F G
头文件及类定义:
//创建一个辅助的node类以及BinaryTree类中多数函数使用BTNode *p的参数是为了实现递归,而同时简化结构
#include<iostream>
#include<stack>
constexpr auto MaxSize = 30;
using namespace std;
typedef char Element;
typedef class node {
public:
Element data;
node* lchild;//指向左孩子节点
node* rchild;//指向右孩子节点
}BTNode;
class BinaryTree {
private:
BTNode* root;//根节点指针
void DelTree(BTNode *p);//私有函数,只被析构函数调用
public:
BinaryTree();//无参构造
BinaryTree(char *str);//有参构造
~BinaryTree();//析构函数不能通过调用自身来释放节点空间,所以采用调用DelTree的方式
BTNode* GetRoot();//返回根节点
BTNode* FindBTNode(BTNode *p,Element x);//查找值为x的节点,返回其指针
BTNode* LchildBTNode(BTNode* p);//返回*p节点的左孩子节点指针
BTNode* RchildBTNode(BTNode* p);//返回*p节点的右孩子节点指针
int Depth_BinaryTree(BTNode *p);//返回树高
void Disp_BinaryTree(BTNode *p);//以括号表示法输出树
//递归遍历算法
void PreOrder(BTNode* p);//先序遍历
void InOrder(BTNode* p);//中序遍历
void PostOrder(BTNode* p);//后序遍历
//非递归遍历算法
void PreOrder2(BTNode* b);
void InOrder2(BTNode* b);
void PostOrder2(BTNode* b);
void LevelOrder(BTNode* b);//层次遍历算法
};
各函数的实现:
两个构造函数:
有参构造函数使用括号表示法表示的合法二叉树为参数,并使用栈作为辅助来构造二叉树。二叉树的结构特点使得其回到某一节点的前驱节点比较困难,因而使用栈保存当前操作节点的前驱节点,使得对某一节点进行操作后能直接(通过出栈)获得其前驱节点指针。后面使用到栈的关于二叉树的算法也是为了实现类似功能。
//无参构造
BinaryTree::BinaryTree() {
root = new BTNode;
}
//有参构造
BinaryTree::BinaryTree(char *str) {//传入一个采用括号表示法表示的合法二叉树
BTNode* p = NULL;
root = NULL;
BTNode* St[MaxSize];//以此栈辅助创建二叉树
int top = -1;
int j = 0;//用于遍历str
int k=0;//用于标识当前节点是栈顶节点的左孩子节点(值为1)或者右孩子节点(值为2),此处赋初值为了减少警告,并无实际效果
char ch=str[j];
while (ch != '\0')
{//遍历str中所有元素
switch (ch)
{
case '(':
top++; St[top] = p; k = 1;
break;
case ')':
top--;
break;
case ',':
k = 2;//k值置为2,标识后面处理的节点为右节点
break;
default:
p = new BTNode();
p->data = ch; p->lchild = p->rchild = NULL;
if (root == NULL)//switch语句的第一次进入直接执行此处
root = p;
else
{
switch (k)
{
case 1:St[top]->lchild = p; break;
case 2:St[top]->rchild = p; break;
default:
break;
}
}
break;
}
ch = str[++j];
}
}
析构函数:
析构函数通过调用DelTree函数实现释放空间的功能。这样做是由于释放空间过程用到了递归,我们将递归过程放到另一个函数中,否则功能将很难实现。
//私有函数,被析构函数调用
void BinaryTree::DelTree(BTNode*p) {
if (p != NULL) {
DelTree(p->lchild);
DelTree(p->rchild);
free(p);
}
}
//析构函数,调用DelTree函数,DelTree函数将树中节点递归删除
BinaryTree::~BinaryTree() {
if (root == NULL)
DelTree(root);
}
返回根节点函数:
添加该函数的目的是为了能在其他函数中(此例中是在main函数中)访问根节点。我们使用两个类(一个BTNode类定义二叉树节点类型,一个BinaryTree类定义对二叉树的各种操作和维持一个root指针)来描述二叉树,在各个重要函数的实现中(所有使用递归的函数)更多的是对BTNode类节点的操作,而这些操作定义在了BinaryTree类中,所以对操作过程的调用必定要用传参方式,这个过程就需要在调用位置获取root指针。当然由此可以想到另一个做法是将函数都放到BTNode类中,取消使用BinaryTree类,但由此会产生一些新的问题,比如root节点怎么维持、怎么为成员开辟空间等。
//返回根节点
BTNode* BinaryTree::GetRoot() {
return root;
}
FindBTNode函数:
BTNode* BinaryTree::FindBTNode(BTNode* p, Element x) {
BTNode* q;
if (p == NULL)
return NULL;
else if (p->data == x)
return p;
else {
q = FindBTNode(p->lchild,x);//递归调用,遍历左子树
if (q != NULL)//在左子树中查找到值为x的节点时返回p
return q;
else
return FindBTNode(p->rchild,x);//递归调用,遍历右子树,此时即便未查找到也返回p
}
}
返回左孩子节点和返回右孩子节点函数:
此处是两个简单例子,它们使用BinaryTree中的函数但操作对象却是BTNode类型。
//返回*p节点的左孩子节点指针
BTNode* BinaryTree::LchildBTNode(BTNode* p) {
return p->lchild;
}
//返回*p节点的右孩子节点指针
BTNode* BinaryTree::RchildBTNode(BTNode* p) {
return p->rchild;
}
Depth_BinaryTree函数:
//返回树高
int BinaryTree::Depth_BinaryTree(BTNode*p) {
int rchilddep,lchilddep;
if (p== NULL)
return 0;
else {
lchilddep = Depth_BinaryTree(p->lchild);//递归求左子树树高
rchilddep = Depth_BinaryTree(p->rchild);//递归求右子树树高
return (lchilddep > rchilddep) ? (lchilddep + 1) : (rchilddep + 1);
}
}
Disp_BinaryTree函数:
//输出树
void BinaryTree::Disp_BinaryTree(BTNode* p) {
if (p != NULL) {
cout << p->data;
if (p->lchild!= NULL || p->rchild != NULL) {//当左节点和右节点有一个不为空即执行下面语句
cout << "(";
Disp_BinaryTree(p->lchild); //处理左子树
if (p->rchild != NULL) //当左子树不为空才输出“,”
cout << ",";
Disp_BinaryTree(p->rchild); //处理右子树
cout << ")"; //左右子树处理完输出")"
}
}
}
三种遍历树的递归函数:
//先序遍历
void BinaryTree::PreOrder(BTNode* p) {
if (p != NULL) {
cout << p->data;
PreOrder(p->lchild);//递归遍历左子树
PreOrder(p->rchild);//递归遍历右子树
}
}
//中序遍历
void BinaryTree::InOrder(BTNode* p) {
if (p != NULL) {
InOrder(p->lchild);
cout << p->data;
InOrder(p->rchild);
}
}
//后序遍历
void BinaryTree::PostOrder(BTNode* p) {
if (p != NULL) {
PostOrder(p->lchild);
PostOrder(p->rchild);
cout << p->data;
}
}
前序遍历(非递归)函数:
与构造函数类似,使用栈获得直接退回某一节点的前驱节点的功能。
执行过程:先将根节点进栈,在栈不为空是循环:出栈p,访问p节点,若其右孩子节点不为空将右孩子节点进栈,若其左孩子节点不为空再将其左孩子节点进栈。
void BinaryTree::PreOrder2(BTNode* b) {
BTNode* St[MaxSize], * p;
int top = -1;
if (b != NULL){
top++;
St[top] = b;//节点入栈
while (top > -1) {
p = St[top]; top--;//出栈并访问栈顶节点
cout << p->data<<" ";//多输出一个空格以区分递归和非递归
if (p->rchild != NULL) {//右孩子进栈,先进栈右孩子以便左孩子先出栈
top++;
St[top] = p->rchild;
}
if (p->lchild != NULL) {//左孩子进栈
top++;
St[top] = p->lchild;
}
}
cout << endl;
}
}
中序遍历(非递归)函数:
执行过程:用指针指向当前要处理的节点。先扫描(并非访问)根节点的所有左节点并将它们一一进栈,当无左节点时表示栈顶元素无左节点,然后出栈这个节点, 并访问它,将p指向刚出栈节点的右孩子,对右子树做同样的处理。需要注意的是,当节点*p的所有左下节点进栈后,这时的栈顶节点要么没有左子树要么其左子树已访问过,就可以访问这个栈顶节点。如此重复操作,直到栈空为止。
void BinaryTree::InOrder2(BTNode* b)
{
BTNode* St[MaxSize], * p;
int top = -1;
p = b;
if (p != NULL)
{
while (top > -1 || p != NULL)
{
while (p!=NULL)
{//扫描p的所有左子节点并将其依次入栈
top++;
St[top] = p;
p = p->lchild;
}
//执行到此处时,栈顶元素没有左孩子节点或左孩子节点均已访问过
if (top > -1)
{
p = St[top];
top--;
cout << p->data << " ";//访问出栈的元素
p = p->rchild;//转向处理右孩子节点
}
}
cout << endl;
}
}
后序遍历(非递归)函数:
采用一个栈保存需要返回的节点指针,先扫描根节点的所有左孩子节点并一一进栈,出栈一个节点 b作为当前节点,然后扫描右子树。当一个节点的左右子树均访问后再访问该节点,如此重复操作,直到栈空为止。
其中难点是如何判断一个节点b的右子树已访问过(实际上右孩子节点已访问过的话,则其右子树就已访问过),为此用p指针保存刚刚访问过的节点(初值为NULL),若b->rchild == p成立(在后序遍历中,b的右孩子节点一定刚好在访问b之前访问),说明b的左右子树均已访问,现在应访问b.
void BinaryTree::PostOrder2(BTNode* b) {
BTNode* St[MaxSize],*p;
int flag, top = -1;
if (b != NULL) {
do {
while (b != NULL)
{//将*b的所有左节点进栈
top++;
St[top] = b;
b = b->lchild;
}
//执行到此处时,栈顶元素没有左孩子节点或左孩子节点均已访问过
p = NULL;
flag = 1;
while (top != -1 && flag)
{
b = St[top];
if (b->rchild == p)
{
cout << b->data << " ";
top--;
p = b;
}
else
{
b = b->rchild;
flag = 0;
}
}
} while (top != -1);
cout << endl;
}
}
层次遍历函数:
在进行层次遍历时,对一层节点访问完后,在按照对它们的访问次序对各个节点的左右孩子顺序访问。这样一层一层进行,先访问的节点其左右孩子也要先访问,与队列的操作原则比较吻合,因此用环形队列来辅助实现。
执行过程:先将根节点进队,在队不为空时循环:从队中出列一个节点*p,访问它;若它有左孩子节点将左孩子节点进队,若它有右孩子节点将右孩子节点进队。如此直到队列为空。
//层次遍历算法
void BinaryTree::LevelOrder(BTNode* b) {
BTNode* p;
BTNode* qu[MaxSize];
int front, rear;
front = rear = -1;
rear++;
qu[rear] = b;
while (front != rear) {//队列不为空时循环
front = (front + 1) % MaxSize;
p = qu[front];//队列头出队
cout << p->data;
if (p->lchild != NULL) {//有左孩子时将其进队
rear = (rear + 1) % MaxSize;
qu[rear] = p->lchild;
}
if (p->rchild != NULL) {//有右孩子时将其进队
rear = (rear + 1) % MaxSize;
qu[rear] = p->rchild;
}
}
cout << endl;
}
主函数测试代码及运行结果:
int main() {
char str[] = "A(B(D(,G)),C(E,F))";//合法树的括号表达式
BTNode* p,*q;
BinaryTree BT(str);//调用有参构造函数构造树
BT.Disp_BinaryTree(BT.GetRoot()); cout << endl;//调用输出函数输出栈
p=BT.FindBTNode(BT.GetRoot(), 'B');//调用查找指定值
if (q=BT.LchildBTNode(p))//调用返回左孩子节点的函数
cout << q->data << endl;
else
cout << "NULL\n";
if (q=BT.RchildBTNode(p))//调用返回右孩子节点的函数
cout << q->data << endl;
else
cout << "NULL\n";
int i = BT.Depth_BinaryTree(BT.GetRoot());//调用求树高函数
cout << i << endl;
//测试三种遍历
BT.PreOrder(BT.GetRoot()); cout << endl;
BT.InOrder(BT.GetRoot()); cout << endl;
BT.PostOrder(BT.GetRoot()); cout << endl;
//测试三种非递归遍历
BT.PreOrder2(BT.GetRoot());
BT.InOrder2(BT.GetRoot());
BT.PostOrder2(BT.GetRoot());
BT.LevelOrder(BT.GetRoot());//层次遍历
return 0;
}