二叉树的遍历--递归与非递归

    对于二叉树而言,有三种遍历方式:前序,中序,后序。由于树本身就是递归定义的,所以递归地去遍历二叉树,不仅容易理解,而且代码简洁。我们知道,递归与栈有关,那我们能否用栈来以非递归的方式实现二叉树的遍历呢?答案是肯定的,不过代码相较递归方式肯定会复杂一些。

    递归是一个调用自身的特殊函数,需要编译器来维护一个栈。那么,我们先了解一下函数与栈的关系。

函数与栈

    当程序执行到某个函数时,将这个函数进行入栈操作,在入栈之前,通常需要完成三件事。
  1、将所有的实参、返回地址等信息传递给被调函数保存。
  2、为被调函数的局部变量分配存储区。
  3、将控制转移到被调函数入口。

    当一个函数完成之后会进行出栈操作,出栈之前同样要完成三件事。
  1、保存被调函数的计算结果。
  2、释放被调函数的数据区。
  3、依照被调函数保存的返回地址将控制转移到调用函数。

    每当运行一个函数时,就在栈顶分配空间,函数退出后,释放这块空间。所以当前运行的函数一定在栈顶。

    递归的过程其实是编译器帮我们处理了压栈和出栈的操作,转换为普通的函数就需要手动地处理压栈和出栈。

前序遍历

    前序遍历是按照 根结点--左孩子--右右孩子的顺序进行遍历

    1.递归实现

template <typename Object>void BinaryTree<Object>::preOrder(BinaryNode<Object> *t) {
    if(t){
        std::cout<<t->element<<"  ";
        preOrder(t->left);
        preOrder(t->right);
    }
}

    2.非递归实现

    前序遍历的访问顺序:优先访问根结点,然后分别访问其左孩子和右孩子。对于任一节点,我们均可将其看做根结点,所以对于每个节点都可以直接访问。访问完一个根结点之后,若其左孩子不为空,则可以按同样的规则去访问他的左子树。在访问其左子树的时候,再去访问他的右子树。

    处理过程如下:

    1.对于任意一个结点p,访问,并将右孩子其入栈,继续访问其左孩子。
    2.判断左孩子是否为空,若为空,弹出栈顶元素并将其赋值给p,访问对应的右孩子。
    3.若栈为空或者p为NULL,遍历结束。

template <typename Object>void BinaryTree<Object>::preOrder(BinaryNode<Object>*root){
    stack<BinaryNode<Object>* >s;
    BinaryNode<Object>*t=root;
    while(!s.empty()||t){
        if(t){
            cout<<t->element<<"  ";
            if(t->right) s.push(t->right);
            t=t->left;
        }
        else{
            t=s.top();
            s.pop();
        }
    }
}

    可以有另外一种思路,对于任一节点p,优先访问当前节点,即保证p在其子节点之前被访问。再分别将右孩子和左孩子入栈,保证左孩子优先于右孩子被访问。

template <typename Object>void BinaryTree<Object>::preOrder(BinaryNode<Object>*root){
    BinaryNode<Object>*t=NULL;
    stack<BinaryNode<Object>*>s;
    s.push(root);
    while(!s.empty()){
        t=s.top();
        s.pop();
        if(t){
            cout<<t->element<<"  ";
            if(t->right)s.push(t->right);
            if(t->left)s.push(t->left);
        }
    }
}

中序遍历

    中序遍历是按照 左孩子--根结点--右孩子的顺序进行遍历

    1.递归实现

template <typename Object>void BinaryTree<Object>::inOrder(BinaryNode<Object> *t) {
    if(t){
        inOrder(t->left);
        cout<<t->element<<"  ";
        inOrder(t->right);
    }
}

    2.非递归实现

    对于任一节点,中序遍历优先访问左孩子,而左孩子由可以看做是一个根结点,然后继续访问其左孩子,直道遇到左孩子为空的根结点。这时访问该节点,然后按相同的规则访问其右子树。

    处理过程如下:1.对于任一节点p,若其左孩子不为空,将p入栈并将p赋值为其左孩子,然后继续对当前p节点进行相同的处理直到p的左孩子为空。
    2.若p的左孩子为空,弹出栈顶元素并访问栈顶节点,然后将p赋值为栈顶节点的右孩子,按相同规则访问当前p的右子树。
    3.若p为NULL或者栈空,遍历结束。

template <typename Object>void BinaryTree<Object>::inOrder(BinaryNode<Object>*root){
    BinaryNode<Object>*t=root;
    stack<BinaryNode<Object>*> s;
    while(!s.empty()||t){
        if(t){
            s.push(t);
            t=t->left;
        }
        else{
            t=s.top();
            s.pop();
            cout<<t->element<<"  ";
            t=t->right;
        }
    }
}

    

后序遍历

    后序遍历的顺序是 左子树--右子树--根结点

    1.递归实现

template <typename Object>void BinaryTree<Object>::postOrder(BinaryNode<Object> *t) {
    if(t){
        postOrder(t->left);
        postOrder(t->right);
        cout<<t->element<<"  ";
    }
}

   2.非递归实现

   对于任一节点,后序遍历是根结点的访问要在其左孩子和右孩子之后。若节点p不存在左孩子和右孩子,则可以直接访问p;若节点p存在左孩子或者右孩子,但已经被访问过了,也可以直接访问p。如果不是这两种情况的话,分别将p的右孩子和左孩子入栈。这样就保证了每次左孩子在右孩子之前被访问,且左孩子和右孩子都在根结点之前被访问。

template <typename Object>void BinaryTree<Object>::postOrder(BinaryNode<Object>*root){
    stack<BinaryNode<Object>*>s;
    BinaryNode<Object>*t=NULL;
    BinaryNode<Object>*pre=NULL;//前一次访问的节点
    s.push(root);
    while(!s.empty()){
        t=s.top();//当前节点
        //如果当前节点是叶子节点,或者其左右孩子都被访问过了
        if((t->left==NULL&&t->right==NULL)||(pre&&(t->left==pre||t->right==pre))){
            cout<<t->element<<"  ";//访问该节点
            s.pop();
            pre=t;//将前一次访问过的节点设置为当前节点
        }
        //非上述情况,将右孩子和左孩子分别入栈
        else{
            if(t->right)s.push(t->right);
            if(t->left)s.push(t->left);
        }
    }
    
}
发布了18 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Juicewyh/article/details/84112643