数据结构之二叉树的遍历

今年暑假准备复习一下数据结构与算法

今天看到二叉树这一节,发现自己对二叉树的遍历的非递归方法还没有掌握,因此写个博客记录一下

typedef struct node
{
    int value;
    struct node * left;
    struct node * right;
} *Binarytree;

首先先摆递归版本

前序遍历

void r_pre_traverse(Binarytree b)
{
    if (b!=NULL)
    {
        printf("%d\n",b->value);
        pre_traverse(b->left);
        pre_traverse(b->right);
    }
    
}

其他递归遍历方法只是换了个位置而已,就不放在这里占位置了

非递归循环版本

中序遍历(中序遍历比较简单先写这个)

首先我们来分析一下主要操作

  1. 寻找左子树,将当前节点压入栈,直到左子树为空
  2. 从栈中弹出来一个节点,访问他的值(出栈即代表访问),然后切换到他的右子树,将右子树压入栈

循环结束的情况分析

  • 节点为空,栈为空
    1. 结束循环
  • 节点不为空,栈为空
    1. 刚刚开始执行,函数还未进入循环体时
    2. 在循环过程中,栈为空即代表当前节点没有父节点,所以此时执行到根节点,正准备切换到根节点的右子树
    3. 也有可能在循环体的访问最后一个右节点时出现
  • 节点为空,栈不为空
    1. 栈不为空代表有父节点,节点为空,说明该节点为st.top()的右子树
  • 节点不为空,栈不为空
    • 这个没什么好说的

总体的流程大概是

while(b!=nullptr||!st.empty()){//右子树为空且栈空时结束
    while(b!=nullptr){//直到左子树为空
        执行1;
    }
    if(!st.empty()){
        执行2;
    }
    
}

丢代码

void inoder_traverse(Binarytree b)
{
    if (b==nullptr)
        return;
    stack<Binarytree > st;
    while (b!=nullptr||!st.empty())
    {   
        while (b!=nullptr)//节点不为空
        {
            st.push(b);//压入当前节点
            b=b->left;//切换到左子树
        }
        //左子树为空,且栈不为空的情况下切换到父节点
        if(!st.empty()){
            b=st.top();
            st.pop(); 

            //访问节点
            cout<<b->value<<endl;
            //如果有右子树,则切换到右子树,没有的话为空,再次循环时也会正常,所以不需要判断
            b=b->right;
        }
    }
    
}

前序遍历

首先我们来分析一下主要操作

  1. 边访问节点边输出当前节点的值,并把节点存入栈中,然后访问左子树,直到访问节点为空
  2. 如果栈不空,从栈中弹出节点,访问他的右子树

循环结束的情况分析

  • 感觉情况和中序遍历差不多的样子,就不列举了

总体的流程大概是

while(b!=nullptr||!st.empty()){
    while(b!=nullptr){
        执行1;
    }
    if(!st.empty()){
        执行2;
    }
}

丢代码

void pre_traverse(Binarytree b)
{
    if (b==nullptr)
    return;
    stack<Binarytree > st;
    while (b!=nullptr||!st.empty())
    {   
        while (b!=nullptr)//节点不为空
        {
            //访问节点
            cout<<b->value<<endl;
            st.push(b);//压入当前节点
            b=b->left;//切换到左子树
        }
        //左子树为空,且栈不为空的情况下切换到父节点
        if(!st.empty()){
            b=st.top();
            st.pop(); 
    
            //如果有右子树,则切换到右子树,没有的话为空,再次循环时也会正常,所以不需要判断
            b=b->right;
        }
    }
}

后序遍历,这个好像有点复杂来着(有两种思路,一种是节点增加一个参数用以确认是否第一次访问,具体方法呢和上面两种差不多,第二种是使用双指针的方法,这里着重讨论第二种)

具体思路

利用两个指针 cur和pre进行操作,其中cur用来保存当前节点的指针,pre前一次访问的节点(pre和cur没有确定的父子关系).要保证根结点在左孩子和右孩子访问之后才能访问,因此对于任一结点P,先将其入栈。

  1. 如果P不存在左孩子和右孩子,则可以直接访问它;
  2. 或者P存在左孩子或者右孩子,但是其左孩子和右孩子都已被访问过了,则同样可以直接访问该结点。
  3. 若非上述两种情况,则将P的右孩子和左孩子依次入栈,这样就保证了每次取栈顶元素的时候,左孩子在右孩子前面被访问,左孩子和右孩子都在根结点前面被访问。

首先我们来分析一下主要操作

  1. 访问节点 直接将节点压入栈中
    • 如果节点不存在子树,直接访问
    • 如果节点存在节点,待按照顺序访问子节点后再从栈中弹出父节点进行访问

循环结束的情况分析(循环开始前将根节点压入)

  • 节点为空,栈为空
    1. 空树,直接过滤
  • 节点不为空,栈为空
    1. 根节点输出完毕,结束循环
  • 节点为空,栈不为空
    1. 右端子树访问完毕,
  • 节点不为空,栈不为空
    • 这个没什么好说的

总体的流程大概是

while(!st.empty()){
    if((cur->left==nullptr&&cur->right==nullptr)||
        ((pre!=nullptr)&&(pre==cur->left||pre==cur->right))){//节点不存在子树,或者子树已经都被访问过了
        执行1;
    }
    else{
        执行2;
    }
}

丢代码

void postorder_traversal(Binarytree b)
{
    if (b==nullptr)
        return;
    Binarytree cur;//是指针哦 保存当前访问的节点
    Binarytree pre=nullptr;
    stack<Binarytree > st;
    st.push(b);//先把根节点压入堆栈
    while (!st.empty())//堆栈不空,堆栈空的时候表示根节点已经被访问了
    {
        cur=st.top();//当前访问的节点为栈顶节点 不弹出
        //节点没有子树
        if ((cur->left==nullptr&&cur->right==nullptr)||
        (pre!=nullptr&&(pre==cur->left||pre==cur->right)))//或者节点的子树都被访问过了,即上一个访问的节点是当前节点的子节点
        {
            //访问当前节点
            cout<<cur->value<<endl;
            st.pop();//弹出堆栈 弹出就代表访问了该节点

            pre=cur;//为下一次访问做准备
        }
        else//存在还未访问的子树,进行子树的访问
        {

            if (cur->left!=nullptr)
                st.push(cur->left);
            if (cur->right!=nullptr)
                st.push(cur->right);
        }
    }
}

这个算法的实现我参考了这个大佬的博客:二叉树的非递归遍历

posted @ 2019-07-11 15:59  _空想家  阅读(704)  评论(0编辑  收藏  举报