菜单

二叉树非递归后缀遍历

2015年7月26日 - 算法

二叉树的遍历方法可分为深度优先和广度优先两种,其中深度优先遍历适合使用栈来辅助实现,广度优先则使用队列,因为栈的先进后出和队列的先进先出特点正好符合遍历顺序的要求。深度优先遍历一般又分为前序遍历,中序遍历,后序遍历,对于一颗树来说,前序、中序、后序针对的都是它的根节点,其中前序遍历访问顺序是:根节点–>左节点–>右节点,中序遍历访问顺序是:左节点–>根节点–>右节点,后续遍历访问顺序是:左节点–>右节点–>根节点。二叉树的结构是递归的,因此非常适合使用递归的方法来完成遍历,而且代码很容易编写,但是如果树的深度很大,递归方法导致函数调用栈太深,而操作系统线程中栈的大小是有限的,函数调用又有一定的开销,因此有时候需要使用非递归的方式来遍历。

在非递归前序遍历时,从根节点向左走,每遇到一个节点,就访问它,然后将该节点进栈,直到再没有左节点。这时候将栈顶节点弹出,如果该栈顶节点没有右节点,则表示以这个节点为根的树已经访问完毕,于是继续弹出栈顶节点进行同样的访问操作,如果该栈顶节点有右节点,则将其右节点进栈,回到循环的开始处,对以这个右节点为根的子树进行遍历。中序遍历方法很类似,区别只在于向左走时不是每遇到一个节点就访问它,而是在再没有左节点时,弹出栈顶节点,然后才访问这个节点,之后对右节点进行和前序遍历一样的操作。后序遍历在向左走时和中序遍历一样,遇到一个节点时先不访问它,而是直接进栈,特别要注意的是直到再没有左节点时后序遍历不能像中序遍历那样弹出栈顶元素。由于是后序遍历,这个栈顶节点需要在它的右子树节点全部被访问之后才能被访问,所以这时候需要先遍历它的右子树,遍历子树这个过程中也要用到栈,等到右子树遍历完成,栈顶元素又变成了刚才那个栈顶节点,这时候就可以弹出栈顶元素并访问该节点,对于以该节点为根的二叉树来说,这个顺序符合后序遍历中根节点是最后一个被访问节点的定义。

从上面对后序遍历的描述中可以知道,向左走时再没有左节点时,我们就开始考查栈顶节点,要对它的右子树进行遍历,而同样的,在其右子树被访问完毕时,栈顶节点还是刚才那个节点,这时候我们就要通过某种手段把这两个状态进行区分,要不然可能又要对这个栈顶节点的右子树进行遍历了,没完没了的。我们可以通过记下最后一个被访问节点的指针,然后每次在考查栈顶节点时,看看最后被访问的节点是不是它的右节点,如果不是那么就处理右子树,如果是则表明其右子树已经遍历完成,这时候可以弹出这个栈顶节点并访问它。所以对于后序遍历,可以写出如下代码:

vector<int> postorderTraversal(TreeNode* root)

{
vector<int> v_ret;
if(!root) return v_ret;

TreeNode* lastVisit = NULL;

stack<TreeNode*> S;
S.push(root);
TreeNode* pNode = S.top();

while(!S.empty())
{
if(pNode)
{
while(pNode->left)
{
S.push(pNode->left);
pNode = pNode->left;
}
}

pNode = S.top();

if(pNode->right)
{
if(lastVisit != pNode->right)
{
S.push(pNode->right);
pNode = pNode->right;
}
else
{
v_ret.push_back(pNode->val);
lastVisit = pNode;
pNode = NULL;
S.pop();
}
}
else
{
v_ret.push_back(pNode->val);
lastVisit = pNode;
pNode = NULL;
S.pop();
}
}

return v_ret;
}

发表评论