二叉树的遍历方法可分为深度优先和广度优先两种。当中深度优先遍历适合使用栈来辅助实现。广度优先则使用队列。由于栈的先进后出和队列的先进先出特点正好符合遍历顺序的要求。
深度优先遍历一般又分为前序遍历,中序遍历。后序遍历,对于一颗树来说,前序、中序、后序针对的都是它的根节点。当中前序遍历訪问顺序是:根节点-->左节点-->右节点,中序遍历訪问顺序是:左节点-->根节点-->右节点,兴许遍历訪问顺序是:左节点-->右节点-->根节点。二叉树的结构是递归的,因此非常适合使用递归的方法来完毕遍历,并且代码非常easy编写。可是假设树的深度非常大。递归方法导致函数调用栈太深。而操作系统线程中栈的大小是有限的,函数调用又有一定的开销。因此有时候须要使用非递归的方式来遍历。
在非递归前序遍历时。从根节点向左走,每遇到一个节点。就訪问它,然后将该节点进栈,直到再没有左节点。这时候将栈顶节点弹出,假设该栈顶节点没有右节点,则表示以这个节点为根的树已经訪问完毕,于是继续弹出栈顶节点进行相同的訪问操作。假设该栈顶节点有右节点。则将其右节点进栈,回到循环的開始处,对以这个右节点为根的子树进行遍历。中序遍历方法非常类似。差别仅仅在于向左走时不是每遇到一个节点就訪问它,而是在再没有左节点时,弹出栈顶节点,然后才訪问这个节点,之后对右节点进行和前序遍历一样的操作。后序遍历在向左走时和中序遍历一样,遇到一个节点时先不訪问它,而是直接进栈,特别要注意的是直到再没有左节点时后序遍历不能像中序遍历那样弹出栈顶元素。因为是后序遍历,这个栈顶节点须要在它的右子树节点所有被訪问之后才干被訪问,所以这时候须要先遍历它的右子树,遍历子树这个过程中也要用到栈。等到右子树遍历完毕。栈顶元素又变成了刚才那个栈顶节点,这时候就能够弹出栈顶元素并訪问该节点,对于以该节点为根的二叉树来说,这个顺序符合后序遍历中根节点是最后一个被訪问节点的定义。
从上面对后序遍历的描写叙述中能够知道。向左走时再没有左节点时,我们就開始考查栈顶节点,要对它的右子树进行遍历。而相同的,在其右子树被訪问完毕时。栈顶节点还是刚才那个节点,这时候我们就要通过某种手段把这两个状态进行区分,要不然可能又要对这个栈顶节点的右子树进行遍历了,没完没了的。
我们能够通过记下最后一个被訪问节点的指针。然后每次在考查栈顶节点时,看看最后被訪问的节点是不是它的右节点,假设不是那么就处理右子树,假设是则表明其右子树已经遍历完毕,这时候能够弹出这个栈顶节点并訪问它。所以对于后序遍历,能够写出例如以下代码:
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;
}