一:二叉树结构
#include<iostream>
#include<stack>
#include<queue>
using namespace std;
struct BiData//节点结构
{
char data;
BiData *lChild, *rChild;
};
class BiTree
{
public:
BiData *root;//根节点
int leafNum = 0;//叶子数,如果此处报错,请将0去掉,并将leafNum的初始化移到构造函数中,此程序在VisualStudio2015编译运行通过
BiTree();//构造函数,构造一个二叉树
BiData * Create();
void PreOrder(BiData * _root);//前序遍历递归
void PreOrderNonRec(BiData * _root);//前序遍历非递归
void InOrder(BiData * _root);//中序遍历递归
void InOrderNonRec(BiData * _root);// 中序遍历非递归
void PostOrder(BiData * _root);//后序遍历递归
void PostOrderNonRec(BiData * _root);//后序遍历非递归
void LevelOrder(BiData * _root);//层次遍历
int Count(BiData * _root);//计算节点
int CountLeaf(BiData * _root);//计算叶子的个数(叶子就是度为0的节点)
};
int main()
{
BiTree myTree;
//1.
cout << "前序遍历递归与非递归\n";
myTree.PreOrder(myTree.root); cout << endl;
myTree.PreOrderNonRec(myTree.root); cout << endl<<endl;
//2.
cout << "中序遍历递归与非递归\n";
myTree.InOrder(myTree.root); cout << endl;
myTree.InOrderNonRec(myTree.root); cout << endl<<endl;
//3.
cout << "后序遍历递归与非递归\n";
myTree.PostOrder(myTree.root); cout << endl;
myTree.PostOrderNonRec(myTree.root); cout << endl << endl;;
//4.
cout << "层次遍历\n";
myTree.LevelOrder(myTree.root); cout << endl << endl;;
//5.
cout << "该二叉树的节点有";
cout << myTree.Count(myTree.root);
cout << "个\n\n";
//6.
cout << "该二叉树的叶子有";
cout << myTree.CountLeaf(myTree.root);
cout << "个\n\n";
return 0;
}
上面的代码实现如下图:
上图myTree变量二叉树如下图
二:具体实现
1.二叉树的构造
对一个二叉树的构造,我使用递归的方法。二叉树的构造和遍历是比较考验对递归的理解的。如果你对递归的理解不到位,那么下面的二叉树三种非递归可能理解会吃力。
BiData * BiTree::Create()
{
char x;
cin >> x;
BiData *p;
if (x == '0')//输入0表示下一个结点结束
p = nullptr;
else
{
p = new BiData;
p->data = x;
//分别构造左子树和右子树
p->lChild = Create();
p->rChild = Create();
}
return p;
}
BiTree::BiTree()//构造二叉树
{
root = Create();
}
2.二叉树遍历-----前序遍历递归与非递归
递归版本的不用多讲了,就是按照“根节点---左孩子----右孩子”的顺序进行遍历。
主要是非递归, 对于任一结点P:
1)访问结点P,并将结点P入栈;
2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
3)直到P为nullptr并且栈为空,则遍历结束。
模拟递归,借助容器stack来实现。优先访问根节点,然后接着左子树和右子树。其实对于每一个节点都可以看作根节点。对于上图的myTree,首先访问和输出根节点(也就是1),然后,左子树(也就是2---4---6----7),接着右子树(也就是3----5)。当访问和输出1后,接着访问和输出左孩子2,因为每一个节点都可以看作根节点,因此此时的2变为一个根节点,接着访问和输出2的左孩子(如果2有左孩子的话,如图来看,2是有左孩子的),依次访问到4,此时没有左孩子了,那就要看右孩子6了,访问6的左孩子,发现没有,接着6的右孩子7,此时7已经是结尾了,既没有左孩子也没有右孩子,至此2的左子树(就是4---6--7)已经遍历完毕了,要开始遍历2的右子树,发现2是没有右子树的,那就结束,自此,1的左子树也遍历完毕。好,接下来就是1的右子树了,依次推,3---5,结束。
void BiTree::PreOrder(BiData * _root)//前序遍历递归
{
if (_root == nullptr)
return;
else
{
cout << _root->data << " ";
PreOrder(_root->lChild);
PreOrder(_root->rChild);
}
}
void BiTree::PreOrderNonRec(BiData * _root)//前序遍历非递归
{
stack<BiData*> s;//模拟一个栈
while (_root != nullptr || !s.empty())
{
while (_root != nullptr)
{
cout << _root->data << " ";
s.push(_root);
_root = _root->lChild;
}
if (!s.empty())
{
_root = s.top()->rChild;
s.pop();
}
}
}
3.二叉树遍历----中序遍历递归与非递归
非递归,对于任一结点P,
1)若其左孩子不为空,则将P入栈并将P的左孩子置为当前的P,然后对当前结点P再进行相同的处理;
2)若其左孩子为空,则取栈顶元素并进行出栈操作,访问该栈顶结点,然后将当前的P置为栈顶结点的右孩子;
3)直到P为nullptr并且栈为空则遍历结束。
中序非递归版本其实就是需要遍历完一个节点的左子树,然后输出根节点,接着右子树。对于myTree,先遍历1的左子树,访问到4时,4没有左子树,输出4,再看其右孩子6.再开始遍历6的左子树,6没有左子树,那就遍历右子树7,至此,2的左子树遍历完成,开始2的右子树,发现为空,此时1的左子树遍历完成,输出1,再开始1的右子树。 其实只要记住先左遍历到底,再根,然后右,其中对于每一个节点,都是有左和右的。这点要记住,就像遍历到4时,4没有左,但是有右,对于4的右孩子6,我们还要遍历6的左孩子,是需要一直访问到底的,但是因为6没有左子树,故而直接转向右子树7了,对于7,我们依旧要遍历7的左子树一直遍历到底,因为其没有左子树,因此看右子树,7没有右子树,结束。
其实就这么简单。
void BiTree::InOrder(BiData * _root)//中序遍历递归
{
if (_root == nullptr)
return;
else
{
InOrder(_root->lChild);
cout << _root->data << " ";
InOrder(_root->rChild);
}
}
void BiTree::InOrderNonRec(BiData * _root)//中序遍历非递归
{
stack<BiData*> s;
while (_root != nullptr || !s.empty())
{
while (_root != nullptr)
{
s.push(_root);
_root = _root->lChild;
}
if (!s.empty())
{
cout << s.top()->data << " ";
_root = s.top()->rChild;
s.pop();
}
}
}
4.二叉树遍历-----后序遍历递归与非递归
非递归,对于每一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了,那就可以输出它了。若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子和右孩子都在根结点前面被访问。也就是先依次遍历左子树和右子树,然后输出根节点。
依旧对于上图中的myTree,因为对于一个节点,如果我们想要输出它,只有它既没有左孩子也没有右孩子或者它有孩子但是它的孩子(左孩子和右孩子,如果它有的话)已经被输出了。那么对于1的左子树来说,一直入栈,直到7,发现没有左子树和右子树了,输出7,此时对于6来说,6没有左子树,而它的右子树也输出完成,因此输出6,再到4,4没有左子树,右子树输出完成,所以输出4,至此,2的左子树输出完成,而2没有右子树,所以输出2,至此,1的左子树完成,那就要开始1的右子树了,同理,先操作完一个节点的左子树和右子树才可以输出它自己,因此直到5,输出5,然后3,此时1的右子树完成,终于可以输出1了。
void BiTree::PostOrder(BiData * _root)//后序遍历递归
{
if (_root == nullptr)
return;
else
{
PostOrder(_root->lChild);
PostOrder(_root->rChild);
cout << _root->data << " ";
}
}
void BiTree::PostOrderNonRec(BiData * _root)//后序遍历非递归
{
if (_root == nullptr)
return;
stack<BiData*> s;//栈
BiData * pre = nullptr;//上一次访问的节点
BiData * cur;//现在访问的节点
s.push(_root);
while (!s.empty())
{
cur = s.top();
if ((cur->lChild == nullptr&&cur->rChild == nullptr) ||
(pre != nullptr) && (pre == cur->lChild || pre == cur->rChild))
{
cout << cur->data << " ";
pre = cur;
s.pop();
}
else
{
if (cur->rChild)
s.push(cur->rChild);
if (cur->lChild)
s.push(cur->lChild);
}
}
}
5.二叉树遍历-----层次遍历
层次遍历就是对于每一层(例如上面的myTree来说,1是第一层;2,3是第二层,4,5是第三层,依次下去),从左到右依次输出,这里需要用到队列,而不是栈,注意队列和栈的区别。对于根节点,先进队列,然后输出该节点,接着将根节点出队列,然后将节点的左右孩子(如果它有左右孩子的话)依次入队列,然后然后只要队列不为空,就取出第一个元素,输出该节点,并将该节点的左右孩子再依次入队列,依次下去。
void BiTree::LevelOrder(BiData * _root)//层次遍历
{
if (_root == nullptr)
return;
queue<BiData*> q;//队列
q.push(_root);
BiData * temp = _root;
while (!q.empty())
{
temp = q.front();
q.pop();
cout << temp->data << " ";
if (temp->lChild)
q.push(temp->lChild);
if (temp->rChild)
q.push(temp->rChild);
}
}
6.计算节点数
int BiTree::Count(BiData * _root)//求节点数
{
if (_root == nullptr)
return 0;
else
return Count(_root->lChild) + Count(_root->rChild) + 1;
}
7.计算叶子数
这里在类里定义了一个leafNum用来记录叶子数
int BiTree::CountLeaf(BiData * _root)//求叶子数
{
if (_root == NULL)
;
else
{
if (!_root->lChild && !_root->rChild)
leafNum++;
else
{
CountLeaf(_root->lChild);
CountLeaf(_root->rChild);
}
}
return leafNum;
}
三:总结
二叉树的实现主要是对递归的理解,仔细观察上面的实现,发现它们的实现都用到了递归。可是呢,大多数同学对递归的理解也只是模模糊糊的。对于递归的理解,网上的博客也是有很多,个人觉得,画图是最好的学习与认知方法,画出递归的具体运行情形,这样再遇到递归时能够在脑海构造出递归。
这里要做出一些补充,关于二叉树的三种非递归遍历,如果大家仔细的话应该能发现这三种实现方式只有前序,中序的实现大致相似,后序相比前两个较为复杂。对于这方面,我也在百度上找到了统一的写法,链接是 更简单的非递归遍历二叉树的方法