常见的二叉树的遍历有三种方式:先序遍历(根结点,左子树,右子树)
中序遍历(左子树,根结点,右子树)
后续遍历(左子树,右子树,根结点)
这三种方式有分别可以用递归和非递归来实现,相对二叉树的遍历递归比非递归简单。其实还有一种层序遍历,是利用队列访问这里不做解释。
一、递归遍历
1、先序遍历
先序遍历就是从根结点开始,把根结点当成一个子问题,先访问父结点,然后访问左子树,等左子树全部访问完后,再访问右子树。 每访问到一个节点时,都把这个节点当成子问题进行上述顺序先访问父节点,然后直到左子树为空或者左子树已经访问过了时,再访问右子树。
void _PrevOrder(Node* root)
{
if (root == NULL)
{
return;
}
cout << root->_data << " ";
_PrevOrder(root->_left);
_PrevOrder(root->_right);
}
2、中序遍历
中序遍历就是先访问左子树,等左子树全部访问完,在访问父结点,父结点访问完,再访问右子树。每访问到一个节点时,都把这个节点当成子问题进行上述顺序访问,直到访问到此节点的左子树为空时或者左子树已经访问过了,再访问其根结点,然后在访问右子树。
void _InOrder(Node* root)
{
if (root == NULL)
{
return;
}
_InOrder(root->_left);
cout << root->_data << " ";
_InOrder(root->_right);
}
3、后续遍历
后续遍历就是先访问左子树,在访问右子树,左右子树都访问完了,再访问父结点。同样的每遇到一个节点都将其看成子问题,直到左右子树都为空,或者已经访问过,在访问父结点。
void _PrevOrder(Node* root)
{
if (root == NULL)
{
return;
}
cout << root->_data << " ";
_PrevOrder(root->_left);
_PrevOrder(root->_right);
}
二、非递归遍历
非递归遍历是借助栈进行遍历,因为遍历二叉树访问完左子树后还必须访问访问右子树,所以我们必须把每个访问过得节点存起来,以方便再访问其节点的右子树。
1、先序遍历
先序遍历的主要步骤:(参考下图)
1.从根结点开始先压左路结点,并访问结点,直到把根结点和左路结点全部压入栈。
2.若左子树和为空,说明左路和根结点已经全部压栈并且已经访问过了,开始取栈顶元素来访问上一层父节点的右子树。把 右子树看成子问题继续进行1步骤。
3.依次进行上述1和2压栈访问和出栈操作,直到栈为空或者右子树为空这说明遍历完毕。
{
Node* cur = _root;
stack<Node*> s;
{
while (cur != NULL)
{
cout << cur->_data << " ";
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
cur = top->_right;
s.pop();
}
cout << endl;
}
{
Node* cur = _root;
stack<Node*> s;
{
while (cur != NULL)
{
cout << cur->_data << " ";
s.push(cur);
cur = cur->_left;
}
Node* top = s.top();
cur = top->_right;
s.pop();
}
cout << endl;
}
2、中序遍历
中序遍历和先序遍历的压栈出栈操作和判断都是是一样的,只是访问结点的顺序是不一样的。所以直接根据上图思想看中序遍历代码。
void InOrderR()
{
Node* cur = _root;
stack<Node*> s;
while (!s.empty() || cur != NULL) //若这里的cur为空,则说明左右子树都为空
{
while (cur != NULL)
{
s.push(cur);
cur = cur->_left;
}
//若左子树为空时程序则走到这里,开始取栈顶元素来打印父节点,并访问父节点的右子树
Node* top = s.top();
cout << top->_data << " ";
cur = top->_right;
s.pop();
}
cout << endl;
}
3、后续遍历
后续遍历的问题:
后续遍历就不一样了,稍微比中序和先序麻烦一点。
因为后续遍历必须是访问完左右子树之后才可以访问父亲结点,所以访问完左子树时,现在得去访问右子树,通过栈找到父亲结点(这时是第一次top父亲结点),然后找到父亲结点的右子树进行访问,当把右子树访问完成后,再通过栈找到父亲结点进行访问(这时是第二次top父亲结点A)。那么怎么才知道这时是第一次top和第二次top呢?如果不做处理的话这里就会一直认为是第一次top父亲节点,不出栈,就会造成死循环,所以怎样解决呢?
解决方法:
创建一个Prev结点,用来记录上一次出栈的结点,若上一次,出栈的结点为右子树,这说明可以访问根结点了,说明是已经第二次top父亲结点了。
void PostOrderR()
{
Node* cur = _root;
stack<Node*> s;
Node* Prev = NULL; //用Prev记录上一次访问的节点,这样就可以判断右子树(不为空时)是第一次访问,还是第二次访问
while (!s.empty() || cur != NULL) //第一次访问会把右子树当成子问题处理,当右子问题输出后,Prev就会 置成右子节点
{ //当在一次回到访问右子树的父节点时,发现上一次的Prev是右子节点,作 说明已经访问过有节点,这可以输出根结点了
while (cur != NULL) //从根结点开始把左子树全部压入栈
{
s.push(cur);
cur = cur->_left;
}
Node* top = s.top(); //左子树为空时,取出栈顶元素
if (top->_right == NULL || top->_right == Prev)//取出栈顶元素,看他的右子树是否为空或者是否右子树已经访 问过,若是则打印当前节点,并把当前节点出栈
{
cout << top->_data << " ";
Prev = top;
s.pop();
}
else //若右子树没访问过,且不为空,则把右子树当成子问题,继续上面的压栈操作
{
cur = top->_right;
}
}
cout << endl;
}
转载自(http://blog.csdn.NET/dream_1996/article/details/72809766?ref=myread)