如前所述,二叉树的遍历一般有四种,即层次、先序、中序、后序。鉴于后序遍历与先序遍历基本构思相同,故这里只就前三种遍历方式作讨论。
先是先序遍历。采用递归的思路来解决先序遍历问题无疑是很容易想到,只要规定访问的次序,让系统先访问该节点,再访问左子树,最后再访问右子树,之后循环递归即可。但是,递归的缺点是显而易见的,那就是由于每一次的递归需要开辟全新的存储空间来进行操作,因此总的时间成本和空间成本在常系数意义上都远大于迭代算法。我们需要另辟蹊径来重新设计迭代的思路。
仔细考察先序遍历的路径,不难发现对于每棵子树而言,都是率先顺序读入最左端的子链并进行写入操作,而右子树的操作又是逆序进行的。这就提示我们,可以采用栈结构来进行先序遍历操作。具体地说,就是在读入每一个节点都将该节点和右孩子节点入栈,并转入左孩子节点作继续的访问,直至没有左孩子时才开始对栈顶的元素开始施行操作。
#include "Stack.h"
template <typename T, typename VST>
void BinTree<T>::travPre(VST &visit)
{
BinNodePosi(T) x = this._root;
Stack<BinNodePosi(T)> S(x);
while (S.size())
{
S = goAlongLeftBrach(x, S, visit);
x = S.pop();
}
Stack<BinNodePosi(T)> goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)> S, VST &visit)
{
while (x.lChild)
{
visit(x.data);
if (x.rChild) S.push(x.rChild);
S.push(x.lChild);
}
return S;
}
其次是中序遍历。采用递归的思路来解决中序遍历问题无疑是很容易想到,只要规定访问的次序,让系统先访问左子树,再访问该节点,最后再访问右子树,之后循环递归即可。但是,递归的缺点是显而易见的,那就是由于每一次的递归需要开辟全新的存储空间来进行操作,因此总的时间成本和空间成本在常系数意义上都远大于迭代算法。我们需要另辟蹊径来重新设计迭代的思路。
仔细考察中序遍历的路径,不难发现对于每棵子树而言,都是率先顺序读入最左端的子链,但是再进行写入操作时,左端子链却又是逆序进行的。这就提示我们,可以采用栈结构来进行先序遍历操作。具体地说,就是在读入每一个节点都将该节点和右孩子节点入栈,并转入左孩子节点作继续的访问,直至没有左孩子时才开始对栈顶的元素开始施行操作。
#include "Stack.h"
template <typename T, typename VST>
void BinTree<T>::travIn(VST &visit)
{
Stack<BinNodePosi(T)> S;
BinNodePosi(T) x = this._root;
do {
S = goAlongLeftBrach(x, S);
x = S.pop();
visit(x.data);
} while (S.size());
}
Stack<BinNodePosi(T)> goAlongLeftBranch(BinNodePosi(T) x, Stack<BinNodePosi(T)> S)
{
while (x.lChild)
{
if (x.rChild) S.push(x.rChild);
S.push(x);
}
return S;
}
最后我们再来考察层次遍历。不难发现,层次遍历的执行次序是逐层展开的,只是一种类似于队列的操作过程,因此可以考虑使用队列来简化算法,其实现如下:
#include "queue.h"
template <typename T, typename VST>
void travLevel(VST &visit)
{
BinNodePosi(T) x = this._root;
queue<BinNodePosi(T)> R(x);
while (!R.empty())
{
R = goAlongLevel(x, R);
visit(x.data);
x = R.dequeue;
}
}
queue<BinNodePosi(T)> goAlongLevel(BinNodePosi(T) x, queue<BinNodePosi(T)> R)
{
if (HasLChild(x)) R.enqueue(x.lChild);
if (HasRChild(x)) R.enqueue(x.rChild);
}