二叉树递归与非递归遍历

来源:互联网 发布:二维数组是线性结构吗 编辑:程序博客网 时间:2024/05/16 00:24

                                                              二叉树的遍历

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

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

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

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

一、递归遍历

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;}


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;}



原创粉丝点击