二叉树递归与非递归遍历

                                                  二叉树的遍历

常见的二叉树的遍历有三种方式:先序遍历(根结点,左子树,右子树)

                                                            中序遍历(左子树,根结点,右子树)

                                                           后续遍历(左子树,右子树,根结点)

这三种方式有分别可以用递归非递归来实现,相对二叉树的遍历递归比非递归简单。其实还有一种层序遍历,是利用队列访问这里不做解释。

一、递归遍历

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压栈访问和出栈操作,直到栈为空或者右子树为空这说明遍历完毕。

 void PrevOrderR()
 {
  Node* cur = _root;
  stack<Node*> s;
  while (!s.empty() || cur != NULL)  //若这里的cur为空,则说明左右子树都为空,只有栈和右子树同时为空时,二叉树才访问完
  {
   while (cur != NULL)
   {
    cout << cur->_data << " ";
    s.push(cur);
    cur = cur->_left;
   }
   //若左子树和为空时程序则走到这里,开始取栈顶元素来访问上一层父节点的右子树
   Node* top = s.top();
   cur = top->_right;
   s.pop();
  }
  cout << endl;
 }


 void PrevOrderR()
 {
  Node* cur = _root;
  stack<Node*> s;
  while (!s.empty() || cur != NULL)  //若这里的cur为空,则说明左右子树都为空,只有栈和右子树同时为空时,二叉树才访问完
  {
   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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值