前言:
这个主要是数据结构实验课的作业,实验的要求如下:
原本我想着这种东西网上搜搜拉到了,但是自己写的时候却出现了不少的问题,因此我决定自己来写一个不会出错的二叉树。
原理:
二叉树的构成其实并不复杂,二叉树的操作也和二叉树的构成差不多,主要要对递归理解的比较深刻就可以轻松地写出二叉树(暂时不论细节)。他的节点和链表的节点差不大多:
一个data域用于数据的存储。
两个指针域,一个指向左子节点(lchild),一个指向右子节点(lchild)。
因此节点的struct构成如下:
typedef struct Node
{
char data;
Node* lchild, * rchild;
}*Broot,*Bnode,node;
//*Broot是因为后面操作经常需要用到根节点,而创建的根节点是个指针,为了方便创建*Broot
//*Bnode同理,后面许多节点是直接用指针表示的。node则是全小写,比较方便后面码代码
然后我们简单的设想一下一棵树需要有的属性和操作:
1.属性:一棵树至少要有一个根吧,至少要能知道这树多少个节点吧。
2.操作:先序,中序,后序遍历;初始化;销毁;拷贝构造;用值去填充树(creat)······
因此抽象出BinaryTree类:
typedef struct Node
{
char data;
Node* lchild, * rchild;
}*Broot,*Bnode,node;
class BinaryTree {
private:
int count;
public:
Broot root; //这地方把root作为共有属性,是因为后头经常要用到,怕麻烦
BinaryTree() :count(0), root(nullptr) {};
bool isEmptytree() { return root; }
void creatTree(Broot& p);//创建有值的二叉树,若子节点为空,用#表示
void prePrint(Bnode p);//先序遍历
void midPrint(Bnode p);//中序遍历
void posPrint(Bnode p);//后序遍历
void midPrint_useNoRecursion(Bnode p);//这个是题目要求的不递归(手写栈)实现中序遍历
};
P.S. 这边我们规定二叉树中没有值的的节点用#表示
实现:
这里我们需要先达成一个共识,我们在初始化这棵树的时候其实这棵树的根是个虚拟的,因为我们并没有往里头存上数据(树的根节点也是存数据的)。因此,我们的构造函数只需要将根指针置空就好,以防后续的未初始化问题。
BinaryTree() :count(0), root(nullptr) {};
判空操作:我们首先要知道空树的定义。空树是没有任何一个节点的树,因此他也不存在根节点,所以我们只需要判断根节点是否存在即可。
bool isEmptytree() { return root; }
递归先序,中序,后序遍历:为啥先说遍历,因为递归的遍历真的很简单···我们首先理解这三种遍历的区别:只是输出数据的位置不同:先序就是先输出,再往下递归左子树,右子树;中序就是先递归左子树,递归出来以后输出,再递归右子树;后续同理。
然后,在什么情况下是要输出的呢?显然是节点存在就输出。因此三种递归遍历实现如下:
void BinaryTree::prePrint(Bnode p)
{
if (p == NULL)return;
cout << p->data;
prePrint(p->lchild);
prePrint(p->rchild);
}
void BinaryTree::midPrint(Bnode p)
{
if (p == NULL)return;
midPrint(p->lchild);
cout << p->data;
midPrint(p->rchild);
}
void BinaryTree::posPrint(Bnode p)
{
if (p == NULL)return;
posPrint(p->lchild);
posPrint(p->rchild);
cout << p->data;
}
P.S. 这边其实也会出问题,哪边呢,复制的时候复制快了,会写成下头这个错样子 /doge
这个是错的,别搞错了
void BinaryTree::midPrint(Bnode p)
{
if (p == NULL)return;
prePrint(p->lchild);
cout << p->data;
prePrint(p->rchild);
}
然后是用值去填充树
这里其实涉及了一个比较有意思的问题:关于二重指针的传址。本文不做过多讨论。我们选择使用一重指针的引用来规避这个比较繁琐的问题。
为什么要传一重指针的引用(这个Broot是一重指针的别名)?因为我们的root是用指针写的,我们需要实际的给这个root创建一个地址。
我们用getchar()函数去一个个的读我们传入的数据,每次递归读一个,读到#终止本次读入。总体上是先向左存,再向右存。实现如下:
void BinaryTree::creatTree(Broot& p) {
char c;
c = getchar();
if (c == '#') {
p = NULL;
}
else {
p = new node;
p->lchild = NULL;
p->rchild = NULL;
p->data = c;
creatTree(p->lchild);
creatTree(p->rchild);
}
}
最后,我们实现最麻烦的用非递归实现的中序遍历。为什么说他麻烦呢,因为他要在函数体里手写一个栈用来存储遍历的顺序,以便回溯。
但是好在我们需要的栈不需要很强大的功能,因此我们用极其简单的顺序栈来实现就足够了,这个顺序栈的每个单元存的应该都是树中遍历过得节点的地址,这样才能实现回溯效果。为了在主体中不输出,我们把中序遍历中输出的功能放到栈的pop中。
为什么是pop里呢?因为中序遍历是在每一个lchild到头以后,退出回到上一层的时候进行的输出,也就是在退栈的时候才进行输出。栈的结构如下:
//这里用struct来写,是因为他的权限默认全是public
struct stack_ptr {
Node* arr[20] = { NULL };
int top = 0;
void push(Node* p) {
arr[top++] = p;
}
Node* pop() {
cout << arr[top - 1]->data;
top--;
return arr[top];
}
};
stack_ptr s;
主体的遍历部分逻辑是这样的:树节点存在或者栈不空的情况下,我们依次把左子树入栈,左子树到头以后退栈,指针回溯,遍历右子树,重复这个过程,直到全部遍历完成(栈为空)。这个地方需要注意的点就是总体的循环条件里头的s.top!=0;
非递归实现的中序遍历代码如下:
void BinaryTree::midPrint_useNoRecursion(Bnode p)
{
struct stack_ptr {
Node* arr[20] = { NULL };
int top = 0;
void push(Node* p) {
arr[top++] = p;
}
Node* pop() {
cout << arr[top - 1]->data;
top--;
return arr[top];
}
};
stack_ptr s;
while (p != NULL || s.top != 0) {
while (p != NULL) { s.push(p); p = p->lchild; };
if (s.top != s.base) {
p = s.pop();
p = p->rchild;
}
}
}
自此实现完成,下面放上总览以及测试用例
类实现总览:
typedef struct Node
{
char data;
Node* lchild, * rchild;
}*Broot,*Bnode,node;
class BinaryTree {
private:
int count;
public:
Broot root;
BinaryTree() :count(0), root(nullptr) {};
bool isEmptytree() { return root; }
void creatTree(Broot& p);//创建有值的二叉树,若子节点为空,用#表示
void prePrint(Bnode p);
void midPrint(Bnode p);
void posPrint(Bnode p);
void midPrint_useNoRecursion(Bnode p);
};
void BinaryTree::creatTree(Broot& p) {
char c;
c = getchar();
if (c == '#') {
p = NULL;
}
else {
p = new node;
p->lchild = NULL;
p->rchild = NULL;
p->data = c;
creatTree(p->lchild);
creatTree(p->rchild);
}
}
void BinaryTree::prePrint(Bnode p)
{
if (p == NULL)return;
cout << p->data;
prePrint(p->lchild);
prePrint(p->rchild);
}
void BinaryTree::midPrint(Bnode p)
{
if (p == NULL)return;
midPrint(p->lchild);
cout << p->data;
midPrint(p->rchild);
}
void BinaryTree::posPrint(Bnode p)
{
if (p == NULL)return;
posPrint(p->lchild);
posPrint(p->rchild);
cout << p->data;
}
void BinaryTree::midPrint_useNoRecursion(Bnode p)
{
struct stack_ptr {
Node* arr[20] = { NULL };
int base = 0;
int top = 0;
void push(Node* p) {
arr[top++] = p;
}
Node* pop() {
cout << arr[top - 1]->data;
top--;
return arr[top];
}
};
stack_ptr s;
while (p != NULL || s.top != 0) {
while (p != NULL) { s.push(p); p = p->lchild; };
if (s.top != s.base) {
p = s.pop();
p = p->rchild;
}
}
}
测试数据:
整体代码:
#include<iostream>
using namespace std;
typedef struct Node
{
char data;
Node* lchild, * rchild;
}*Broot,*Bnode,node;
class BinaryTree {
private:
int count;
public:
Broot root;
BinaryTree() :count(0), root(nullptr) {};
bool isEmptytree() { return root; }
void creatTree(Broot& p);//创建有值的二叉树,若子节点为空,用#表示
void prePrint(Bnode p);
void midPrint(Bnode p);
void posPrint(Bnode p);
void midPrint_useNoRecursion(Bnode p);
};
void BinaryTree::creatTree(Broot& p) {
char c;
c = getchar();
if (c == '#') {
p = NULL;
}
else {
p = new node;
p->lchild = NULL;
p->rchild = NULL;
p->data = c;
creatTree(p->lchild);
creatTree(p->rchild);
}
}
void BinaryTree::prePrint(Bnode p)
{
if (p == NULL)return;
cout << p->data;
prePrint(p->lchild);
prePrint(p->rchild);
}
void BinaryTree::midPrint(Bnode p)
{
if (p == NULL)return;
midPrint(p->lchild);
cout << p->data;
midPrint(p->rchild);
}
void BinaryTree::posPrint(Bnode p)
{
if (p == NULL)return;
posPrint(p->lchild);
posPrint(p->rchild);
cout << p->data;
}
void BinaryTree::midPrint_useNoRecursion(Bnode p)
{
struct stack_ptr {
Node* arr[20] = { NULL };
int base = 0;
int top = 0;
void push(Node* p) {
arr[top++] = p;
}
Node* pop() {
cout << arr[top - 1]->data;
top--;
return arr[top];
}
};
stack_ptr s;
while (p != NULL || s.top != 0) {
while (p != NULL) { s.push(p); p = p->lchild; };
if (s.top != s.base) {
p = s.pop();
p = p->rchild;
}
}
}
int main()
{
BinaryTree b;
b.creatTree(b.root);
cout << "先序遍历" << endl;
b.prePrint(b.root);
cout << endl;
cout << "中序遍历" << endl;
b.midPrint(b.root);
cout << endl;
cout << "非递归的中序遍历" << endl;
b.midPrint_useNoRecursion(b.root);
cout << endl;
cout << "后序遍历" << endl;
b.posPrint(b.root);
cout << endl;
return 0;
}
输入及运行结果:
至此,用类实现的二叉树结束了。