目录
二叉树大家都非常熟悉,二叉树的遍历大家也不会陌生。用递归法来实现二叉树的“前中后序遍历”相对简洁,用几行代码就能实现,并且只需改变代码顺序就能实现3种遍历。与之相反,非递归(迭代)遍历方法实现“前中后序遍历”起来较为复杂,并且不太容易通过简单的交换代码的次序来实现3种遍历,当然,只是不太容易,并不是没有方法。 下面就介绍统一的非递归(迭代)方法来是实现“前中后序遍历”。
这个过程要使用栈的数据结构,这点想来不用多解释。
树节点的定义如下:
template<class T>
class TreeNode
{
public:
T data;
TreeNode* leftChild;
TreeNode* rightChild;
};
首先明确一点:树中的任何一个节点都可以是父节点
。每个父节点总是有左右两个子节点(空的子节点暂时也算是一个节点)。做遍历时,按照遍历的方式,依次把父节点和左右两个子节点压入栈中。此处以“后序遍历”为例,那么入栈的顺序就是父节点、右节点、左节点,按照思路,可以写出如下代码:
void BinaryTree<T>::PostOrderIterate(TreeNode<T> * node)
{
std::stack<TreeNode<T> *> st;//定义一个栈
TreeNode<T> * curnode = node;
if (curnode == nullptr) return;
//将父节点和两个子节点压入栈中,若子节点没有,不用压入栈
st.push(curnode);
if (curnode->rightChild) st.push(curnode->rightChild);
if (curnode->leftChild) st.push(curnode->leftChild);
while (!st.empty()){
//每次top出的栈顶数据可能是父节点或子节点。但是,不管哪种节点,现在都作为一个父节点,那么其入栈顺序要变,所以pop掉,重新入栈
curnode = st.top();
st.pop();
//按遍历顺寻入栈
st.push(curnode);
if (curnode->rightChild) st.push(curnode->rightChild);
if (curnode->leftChild) st.push(curnode->leftChild);
}
}
写完了上述代码,我们继续分析。现在我们还需要解决处理数据(此处指打印节点)的问题,那么,什么时候处理数据(打印节点)
呢?还有上述代码的循环永远结束不了,如下图。‘+’会不停的入栈出栈。
分析之前,明确一点:树中的任何一个节点都可以是父节点
。但并不总是父节点,有些节点在某些时候还只是左右子节点,只有在节点出栈的时候才可能成为父节点
。如上图,以‘-’为例。
(1)‘-’作为‘+’的左节点入栈:
(2)‘-’出栈,为左节点出栈:
(3)‘-’作为父节点入栈:
1)先分析‘+’不断出入栈的问题。这个过程如下:‘+’作为左节点入栈,然后作为左节点出栈,再作为父节点入栈,之后一直是作为父节点不断出入栈。可以发现,当节点作为父节点出栈时,此时表示已经到达分支的最下节点,可以结束循环了。
这一点可以类推到右节点。
2)在分析处理数据(打印节点)的问题。先明确一点:节点作为左右子节点的时候是不能打印数据的
,因为我们无法知道它是否为最后一个节点,只有作为父节点的时候才能打印数据
。一但从栈中弹出的节点是父节点,此时一定处理数据(打印节点)。
综合1)和2),不难发现,我们只需要知道出栈的节点是父节点,就能解决问题
。所以,我们只需要在节点入栈时,标记一下入栈节点的属性(父还是子),就可以了,做法是在父节点入栈后,再压入一个标志节点,这样每次取出父节点之前,都是先取出标志节点。
代码如下,注释已详细说明:
void BinaryTree<T>::PostOrderIterate(TreeNode<T> * node)
{
std::stack<TreeNode<T> *> st;//定义一个栈
TreeNode<T> * curnode = node;
TreeNode<T> * tagFatherNode = new TreeNode<T>;//定义一个标志节点,用于标志入栈的父节点
if (curnode == nullptr) return;
//将父节点和两个子节点压入栈中,若子节点没有,不用压入栈
st.push(curnode);
st.push(tagFatherNode);//标记入栈节点为父节点
if (curnode->rightChild) st.push(curnode->rightChild);
if (curnode->leftChild) st.push(curnode->leftChild);
while (!st.empty()){
/*若top出的栈顶数据是一个父节点,处理数据(打印节点)并删除节点
若top出的栈顶数据不是一个父节点,其作为父节点,重新入栈*/
curnode = st.top();//取出栈顶数据
st.pop();
if (curnode == tagFatherNode){//判断出栈的节点是否为父节点
curnode = st.top();//取出父节点
std::cout << curnode -> data; //打印
st.pop();//删除
}
else{
//按遍历顺寻入栈
st.push(curnode);
st.push(tagFatherNode);//标记入栈节点为父节点
if (curnode->rightChild) st.push(curnode->rightChild);
if (curnode->leftChild) st.push(curnode->leftChild);
}
}
delete tagFatherNode;
}
进行到这里,后续遍历的非递归(迭代)方法就写出来,当然不仅仅局限于后序遍历,前序遍历和中序遍历只需调整3行代码的顺序就能实现,如下:
前序遍历:
if (curnode->rightChild) st.push(curnode->rightChild);
if (curnode->leftChild) st.push(curnode->leftChild);
st.push(curnode);
st.push(tagFatherNode);//标记入栈节点为父节点
中序遍历:
if (curnode->rightChild) st.push(curnode->rightChild);
st.push(curnode);
st.push(tagFatherNode);//标记入栈节点为父节点
if (curnode->leftChild) st.push(curnode->leftChild);
这样就写出了和递归遍历方法一样的,只需调整代码顺序,就能实现3种遍历的方法。
总结:这种统一的方法开始会不太好理解,不过没关系,自己多想想,动手写写就好了。注意把握住二叉树的特点:任何节点都可以是父节点,也都可以是左右子节点(根节点处除外),子节点出栈的时候才可能成为父节点;若出栈的就是父节点,处理节点并从栈中删除。
以上就是统一实现3种遍历的方法。如果有疑问,欢迎评论区下方留言;本人水平有限 ,如有错误,也欢迎在评论区下方批评指正。若是喜欢本文,就帮忙点赞吧!