1.中序遍历
中序遍历的思路分析:第一次遍历到节点,要一直遍历到此节点左子树上最左侧的节点,此过程将遍历到的节点按先后顺序依次入栈。到大最左侧时,将栈顶元素出栈并输出,并将此节点的右子树入栈,然后再从头开始,是不是明显看到递归的影子了!
代码如下:
stack<Node*> s;
void InOrder(Node *pRoot)
{
if(pRoot==NULL)
{
return;
}
Node *p=pRoot;
s.push(p);
while(!s.empty())
{
p=s.top();
while(p)
{
p=p->m_pLeft;
s.push(p);
}
s.pop();
if(!s.empty())
{
Node *p=s.top();
s.pop();
cout<<p->m_chValue<<" ";
s.push(p->m_pRight);
}
}
}
注意:上面代码19行处if(!s.empty)这条判断语句是不能省略的。因为当栈为空的时候,输出节点会出错。具体分析为什么要加这个判定语句,从代码看,while(不为空)进入循环,在进入if()判定之前有一个pop动作,所以这有可能使栈为空,是什么情况呢?就是在访问中序遍历的最后一个节点时(”最右“节点)。中序遍历访问的最后一个节点是“最右"节点(即它的右子树肯定为空,如果它的右子树非空,那么遍历会进入右子树,那么就不是最后访问的节点)。这个”最右"元素Node *p=top(),s.pop()出栈并输出。然后s.push(p->m_Right)(p->m_Right为空),所以NULL入栈。接下来进入while循环,栈中只有一个NULL,所以弹出NULL之后栈为空,如果不做判定直接输出会报错。分析这个过程的最简单的例子是分析一棵只有一个节点的树(这个节点就是”最右"节点)来体会上述分析过程。
2.后续遍历
后续遍历的非递归方式是最复杂的。要保证根节点在左右子树访问之后访问,对于任何一个节点p,先将其入栈,如果p没有左右子树,直接出栈访问,或者有左/右子树,但是已经访问过了,同样可以出栈并访问。如果不是这俩种情况,我们先将右子树入栈,再将左子树入栈,这样保证了先访问左子树,在访问右子树,最后访问根节点。
这里的关键是怎么确定当前节点cur的左右子树是否应经访问,可以使用pre指向当前节点cur前一个访问的节点,如果pre=cur->left(没有右子树)或者pre=cur->right(有右子树),则表明cur的左右子树已经访问。
代码:
void PostOrder(Node *pRoot)
{
Node *cur,*pre=NULL;
if(pRoot==NULL)
{
return;
}
s.push(pRoot);
while(!s.empty())
{
cur=s.top();
//当前节点没有左右子树或者左右子树应经访问过
if((cur->m_pLeft==NULL && cur->m_pRight==NULL)||(pre!=NULL&&(pre==cur->m_pLeft||pre==cur->m_pRight)))
{
s.pop();
cout<<cur->m_chValue<<" ";
pre=cur;
}
else
{
if(cur->m_pRight)
s.push(cur->m_pRight);
if(cur->m_pLeft)
s.push(cur->m_pLeft);
}
}
}
3.前序遍历非递归
思路分析:
如果看懂了上面的后续遍历,相信会很快写出前序遍历。前序遍历会先遍历根节点,然后遍历左、右子树。我们将栈顶元素出栈并输出,如果根节点有左右子树,然后将右子树先入栈,再将左子树入栈。
代码如下:
void PreOrder(Node *pRoot)
{
if(pRoot==NULL)
{
return;
}
s.push(pRoot);
while(!s.empty())
{
Node *p=s.top();
cout<<p->m_chValue<<" ";
s.pop();
if(p->m_pRight)
s.push(p->m_pRight);
if(p->m_pLeft)
s.push(p->m_pLeft);
}
}
参考:http://www.cnblogs.com/dolphin0520/archive/2011/08/25/2153720.html