数据结构和算法-二叉树的遍历 二叉树的前中后和层序遍历详细图解(递归和非递归写法) java实现二叉树的遍历(递归和非递归) 二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归) 前言 层序遍历 前中后序遍历(递归) 非递归前序 非递归中序 非递归后序※ 总结

参考:

https://blog.csdn.net/monster_ii/article/details/82115772

https://blog.csdn.net/wang454592297/article/details/79472938

https://www.cnblogs.com/bigsai/p/11393609.html

遍历一棵二叉树常用的有四种方法,前序(PreOrder)、中序(InOrder)、后序(PastOrder)还有层序(LevelOrder)。
前中后序三种遍历方式都是以根节点相对于它的左右孩子的访问顺序定义的。例如根->左->右便是前序遍历,左->根->右便是中序遍历,左->右->根便是后序遍历。
而层序遍历是一层一层来遍历的。

树的前中后序遍历是个递归的定义,在遍历到根节点的左/右子树时,也要遵循前/中/后序遍历的顺序,例如下面这棵树:
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
前序遍历:ABDECFG
中序遍历:DBEAFCG
后序遍历:DEBFGCA
层序遍历:ABCDEFG


树的结点结构体声明如下:
语言:C语言(为了省事用到了C++的栈,因为C语言要用栈的话要自己重新写一个出来,就偷了个懒)
编译器:VS

typedef char DataType;

typedef struct TreeNode{
    DataType data;
    struct TreeNode *left;
    struct TreeNode *right;
}TreeNode;

前序遍历(先序遍历)

对于一棵树的前序遍历,递归的写法是最简单的(写起来),就是将一个大的问题转化为几个小的子问题,直到子问题可以很容易求解,最后将子问题的解组合起来就是大问题的解。

前序访问的递归写法

先放代码,如果看完觉得不太清楚可以看看下面的详细步骤图解。

void PreOrder(const TreeNode *root)
{
    if (root == NULL)                 //若结点为空
    {
        printf("# ");
        return;
    }
    printf("%c ", root->data);        //输出根节点的值
    PreOrder(root->left);             //前序访问左子树
    PreOrder(root->right);            //前序访问右子树
}

比如说还是上面的这颗树:
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

  1. 访问根节点
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  2. 访问左子树
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    走到这里之后发现根节点的左孩子还是一棵子树,那就将访问这棵子树看作是遍历整颗树的一个子问题,遍历这棵子树的方法和遍历整颗树的方法是一样的。
    然后继续访问它的左子树:
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    为了理解起来方便一点,我在这里加上了它的两个为空的左右孩子
    然后发现这(可能)还是一棵子树,就继续用这种方法来对待这颗子树,就是继续访问它的左子树:
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    发现这是一个空节点,那就直接返回,去访问它的右子树:
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    发现还是一个空节点,那么继续返回,这时候D和它的左右孩子结点都访问过了,继续返回,应该访问B的右子树了。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    然后就和D结点一样的处理方法,->左孩子,发现是空,返回->右孩子,发现还是空,继续返回,发现这时候B的左右孩子都访问过了,继续返回。
  3. 访问右子树
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    然后和处理A的左子树的方法一样,最后访问到G结点的右子树时,发现是空,就返回,这时候树的所有节点都已经访问过了,所以可以一路返回到A结点的右子树完的地方,整个递归就结束了。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    最后输出的前序访问序列便是:ABDECFG

前序访问的非递归写法

还是先上代码:

void PreOrderLoop(TreeNode *root)
{
    std::stack<TreeNode *> s;
    TreeNode *cur, *top;
    cur = root;
    while (cur != NULL || !s.empty())
    {
        while (cur != NULL)
        {
            printf("%c ", cur->data);
            s.push(cur);
            cur = cur->left;
        }

        top = s.top();
        s.pop();

        cur = top->right;
    }
}

非递归的写法比递归写法要麻烦一点,要用到栈来存储树的结点,在理解非递归方法的时候要重点理解栈中保存的元素的共同点是什么,在前序访问中,栈中元素都是自己和自己的左孩子都访问过了,而右孩子还没有访问到的节点,如果不太懂可以看下面的详细步骤图解。
  1. 首先我们要用一个指针(cur)来指向当前访问的结点
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    发现这个节点不为空,就将它的数据输出,然后将这个节点的地址(图上的栈中写了节点的值是为了便于理解,实际上栈中保存的是节点地址)压栈。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    再去访问它的左子树,发现左孩子结点依旧不为空,继续输出并压栈。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    同理压栈D节点
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    然后访问D的左孩子,发现为空,便从栈中拿出栈顶结点top,让cur = top->right,便访问到了D的右孩子。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    发现D的右孩子还是为空,这个看一下栈,发现栈不为空,说明还存在右孩子没被访问过的节点,就继续从栈中拿出栈顶结点top,让cur = top->right,便访问到了B的右孩子。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    B的右孩子处理方法和D一样,然后再从栈中拿出A节点,去访问A的右孩子C,在访问到G节点的右孩子之后,发现当前节点cur为空,栈中也没有元素可以取出来了,这时候就代表整棵树都被访问过了,便结束循环。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    最后输出的前序访问序列便是:ABDECFG

中序遍历

对于一棵树的中序遍历,和前序一样,可以分为递归遍历和非递归遍历,递归遍历是相对简单的,还是子问题思想,将一个大问题分解,直到可以解决,最后解决整个大问题。

中序遍历的递归写法

还是先上代码:

void InOrder(const TreeNode *root)
{
    if (root == NULL)              //判断节点是否为空
    {
        printf("# ");
        return;
    }
    InOrder(root->left);           //中序遍历左子树
    printf("%c ", root->data);     //访问节点值
    InOrder(root->right);          //中序遍历右子树
}

从根节点进入
  1. 数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  2. 发现根节点不为空,访问左子树

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
发现不为空,继续访问左子树
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
发现不为空,继续访问左子树
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
这时root为空了,就返回去访问它的根节点,刚才的访问只是路过,并没有真正地遍历节点的信息,在返回途中才是真正地遍历到了节点的信息。
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
访问到了D节点,下来要访问的是D的右孩子,因为D的左孩子已经访问过了。
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
发现还是空,就返回,而它的根节点D也访问过了,那么就继续返回,该访问D节点的父节点B了。
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
B访问过后下来要访问的是B的右孩子,因为是从B的左子树回来的路,B的左孩子已经访问过了。
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
然后和访问D一样,->左孩子,为空,返回访问根节点E,->右孩子,为空(这部分就不画了,和D节点的访问是一样的),最后返回,B已经访问过了,就继续返回,至此,整颗树的左子树访问完了。
3. 访问B的根节点A
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
4. 遍历A的右子树
遍历右子树的过程和左子树一样,还是左->根->右的中序遍历下去,直到遍历到G的右孩子,发现为空,就返回,因为右子树都遍历过了,所以可以一直返回到root为A节点的那一层递归,整个遍历结束。

最后输出的中序访问序列为:DBEAFCG

非递归写法

中序访问的非递归写法和前序一样,都要用到一个栈来辅助存储,不一样的地方在于前序访问时,栈中保存的元素是右子树还没有被访问到的节点的地址,而中序访问时栈中保存的元素是节点自身和它的右子树都没有被访问到的节点地址。

先上代码:

void InOrderLoop(TreeNode *root)
{
    std::stack<TreeNode *> s;
    TreeNode *cur;
    cur = root;
    while (cur != NULL || !s.empty())
    {
        while (cur != NULL)
        {
            s.push(cur);
            cur = cur->left;
        }

        cur = s.top();
        s.pop();
        printf("%c ", cur->data);

        cur = cur->right;
    }
}
  1. cur指针一路沿着最左边往下访问,路过的节点全部压栈,直到遇到空节点
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  2. 从栈中取出栈顶节点top,输出栈顶结点的值并使cur = top->right,从第一步开始去遍历top的右子树。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    遍历完之后,cur走到了D节点的右孩子,发现cur 为空,但栈中还有元素,就重复第二步
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    这时候,cur走到了E节点的右孩子,发现cur 为空,但栈中还有元素,就继续重复第二步,之后cur = top->right,cur指针继续去遍历A节点的右子树,从第一步开始
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    访问到F的左孩子节点发现是空,这时候栈中还有元素,就重复第二步
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    照这个规则依次访问下去,最后会访问到G节点的右孩子,这时候cur为空,栈也空了,就代表所有节点已经遍历完了,就结束循环,遍历完成。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

最后输出的中序访问序列为:DBEAFCG


后序遍历

后序遍历还是分递归版本和非递归版本,后序遍历的递归版本和前序中序很相似,就是输出根节点值的时机不同,而后序遍历的非递归版本则要比前序和中序的要难一些,因为在返回根节点时要分从左子树返回和右子树返回两种情况,从左子树返回时不输出,从右子树返回时才需要输出根节点的值。

递归写法

先上代码:

void PostOrder(TreeNode *root)
{
    if (root == NULL)
    {
        printf("# ");
        return;
    }
    PostOrder(root->left);
    PostOrder(root->right);
    printf("%c ", root->data);
}

后序遍历的递归版本和前中序非常相似,就是输出根节点值的时机不同,详细图解这里就不画了,可以联系前中序的递归版本来理解。

后序遍历的非递归写法

后序遍历的非递归同样要借助一个栈来保存元素,栈中保存的元素是它的右子树和自身都没有被遍历到的节点,与中序遍历不同的是先访问右子树,在回来的时候再输出根节点的值。需要多一个last指针指向上一次访问到的节点,用来确认是从根节点的左子树返回的还是从右子树返回的。

先上代码:

void PostOrderLoop(TreeNode *root)
{
    std::stack<TreeNode *> s;
    TreeNode *cur, *top, *last = NULL;
    cur = root;
    while (cur != NULL || !s.empty())
    {
        while (cur != NULL)
        {
            s.push(cur);
            cur = cur->left;
        }

        top = s.top();

        if (top->right == NULL || top->right == last){
            s.pop();
            printf("%c ", top->data);
            last = top;
        }
        else {
            cur = top->right;
        }
    }
}
  1. 还是沿着左子树一路往下走,将路过的节点都压栈,直到走到空节点。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  2. 然后从栈中看一下栈顶元素(只看一眼,用top指针记下,先不出栈),如果top节点没有右子树,或者last等于top的右孩子,说明top的右子树不存在或者遍历过了,就输出top节点的值,并将栈顶元素pop掉(出栈),反之则是从左子树回到根节点的,接下来要去右子树。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    如图,top的右孩子为空,说明右子树不存在,就可以输出top的值并pop掉栈顶了,这时候用last指针记下top指向的节点,代表上一次处理的节点。(这一过程cur始终没有动,一直指向空)
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    继续从栈顶看一个元素记为top,然后发现top的右孩子不为空,而且last也不等于top->right,就使cur = top->right,回到第一步,用同样的方法来处理top的右子树,下一次回来的时候,last指针指向的是E节点。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    这时候发现top的右孩子不为空,但是last等于top->right,说明top的右子树遍历完成,下一步就要输出top的值并且将这个节点出栈,下一次再从栈中看一个栈顶元素A即为top。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    这时候再比较,发现top的right不为空,而且last也不等于top->right,说明top有右子树并且还没有遍历过,就让cur = top->right,回到第一步用同样的方法来遍历A的右子树。
    到最后,cur访问到了G的左孩子,而top也一路出栈到了A节点,发现cur为空,并且栈中也为空,这时候便代表整个树已经遍历完成,结束循环。

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

最后输出的中序访问序列为:DEBFGCA

层序遍历

层序遍历是比较接近人的思维方式的一种遍历方法,将二叉树的每一层分别遍历,直到最后的叶子节点被全部遍历完,这里要用到的辅助数据结构是队列,队列具有先进先出的性质。

上代码:

void LevelOrder(TreeNode *root)
{
    std::queue<TreeNode *> q;
    TreeNode *front;

    if (root == NULL)return;

    q.push(root);

    while (!q.empty())
    {
        front = q.front();
        q.pop();

        if (front->left)
            q.push(front->left);

        if (front->right)
            q.push(front->right);

        printf("%c ", front->data);
    }
}

层序遍历的思路是,创建一个队列,先将根节点(A)入队,然后用front指针将根节点记下来,再将根节点出队,接下来看front节点(也就是刚才的根节点)有没有左孩子或右孩子,如果有,先左(B)后右(C)入队,最后输出front节点的值,只要队列还不为空,就说明还没有遍历完,就进行下一次循环,这时的队头元素(front)则为刚才入队的左孩子(B),然后front出队,再把它的左右孩子拉进来(如果有),因为队列的先进先出性质,B的左右孩子DE是排在C后面的,然后输出B,下一次循环将会拉人C的左右孩子FG,最后因为FG没有左右孩子,一直出队,没有入队元素,队列迟早会变为空,当队列为空时,整颗树就层序遍历完成了,结束循环。

  1. 根节点入队,并用front指针标记
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  2. 队头出队,并将左右孩子拉进队列
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    重复1,2
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
  3. 直到队列为空
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    这时候便代表整个树遍历完成,结束循环。

最后输出的层序访问序列为:ABCDEF

java实现二叉树的遍历(递归和非递归)

源码地址:
https://github.com/TimePickerWang/aimed-at-offer/blob/master/java%E6%BA%90%E7%A0%81/TreeInfo.java

现有一颗如下图所示的二叉树:

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

其遍历的各种方式如下:

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

构造一颗如下图所示的二叉树,用java实现其前序,中序,后序遍历

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

注意二叉树节点的定义如下:

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

递归实现:

// 用递归的方法进行先序遍历
    public void qinaxuDigui(TreeNode treeNode) {
        qianxuNumList.add(treeNode.val);
        if (treeNode.left != null) {
            qinaxuDigui(treeNode.left);
        }
        if (treeNode.right != null) {
            qinaxuDigui(treeNode.right);
        }
    }

    // 用递归的方法进行中序遍历
    public void zhongxuDigui(TreeNode treeNode) {
        if (treeNode.left != null) {
            zhongxuDigui(treeNode.left);
        }
        zhongxuNumList.add(treeNode.val);
        if (treeNode.right != null) {
            zhongxuDigui(treeNode.right);
        }
    }

    // 用递归的方法进行后序遍历
    public void houxuDigui(TreeNode treeNode) {
        if (treeNode.left != null) {
            houxuDigui(treeNode.left);
        }
        if (treeNode.right != null) {
            houxuDigui(treeNode.right);
        }
        houxuNumList.add(treeNode.val);
    }

非递归实现:
// 用非递归的方法进行先序遍历
    public void qinaxuFeiDigui(TreeNode treeNode) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (treeNode != null || !stack.isEmpty()) {
            while (treeNode != null) {
                qianxuNumList.add(treeNode.val);
                stack.push(treeNode);
                treeNode = treeNode.left;
            }
            if(!stack.isEmpty()){
                treeNode = stack.pop();
                treeNode = treeNode.right;
            }
        }
    }

    // 用非递归的方法进行中序遍历
    public void zhongxuFeiDigui(TreeNode treeNode) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (treeNode != null || !stack.isEmpty()) {
            while (treeNode != null) {
                stack.push(treeNode);
                treeNode = treeNode.left;
            }
            if (!stack.isEmpty()) {
                treeNode = stack.pop();
                zhongxuNumList.add(treeNode.val);
                treeNode = treeNode.right;
            }
        }
    }

    // 用非递归的方法进行后序遍历
    public void houxuFeiDigui(TreeNode treeNode) {
        Stack<TreeNode> stack = new Stack<TreeNode>();
        while (treeNode != null || !stack.isEmpty()) {
            while (treeNode != null) {
                stack.push(treeNode);
                treeNode = treeNode.left;
            }
            boolean tag = true;
            TreeNode preNode = null;  // 前驱节点
            while (!stack.isEmpty() && tag == true) {
                treeNode = stack.peek();
                if (treeNode.right == preNode) { // 之前访问的为空节点或是栈顶节点的右子节点
                    treeNode = stack.pop();
                    houxuNumList.add(treeNode.val);
                    if (stack.isEmpty()) {
                        return;
                    } else {
                        preNode = treeNode;
                    }
                } else {
                    treeNode = treeNode.right;
                    tag = false;
                }
            }
        }
    }

运行结果如下:

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)

前言

  • 前面介绍了二叉排序树的构造和基本方法的实现。但是排序遍历也是比较重要的一环。所以笔者将前中后序.和层序遍历梳理一遍。
  • 了解树的遍历,需要具有的只是储备有队列,递归,和栈。这里笔者都有进行过详细介绍,可以关注笔者数据结构与算法专栏。持续分享,共同学习。

层序遍历

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
层序遍历。听名字也知道是按层遍历。我们知道一个节点有左右节点。而每一层一层的遍历都和左右节点有着很大的关系。也就是我们选用的数据结构不能一股脑的往一个方向钻,而左右应该均衡考虑。这样我们就选用队列来实现。

  • 对于队列,现进先出。从根节点的节点push到队列,那么队列中先出来的顺序是第二层的左右(假设有)。第二层每个执行的时候添加到队列,那么添加的所有节点都在第二层后面
  • 同理,假设开始pop遍历第n层的节点,每个节点会push左右两个节点进去。但是队列先进先出。它会放到队尾(下一层)。直到第n层的最后一个pop出来,第n+1层的还在队列中整齐排着。这就达到一个层序的效果。

实现的代码也很容易理解:

public void cengxu(node t) {//层序遍历
	Queue<node> q1 = new ArrayDeque<node>();
	if (t == null)
		return;
	if (t != null) {
		q1.add(t);
	}
	while (!q1.isEmpty()) {
		node t1 = q1.poll();
		if (t1.left != null)
			q1.add(t1.left);
		if (t1.right != null)
			q1.add(t1.right);
		System.out.print(t1.value + " ");
	}
	System.out.println();
}

前中后序遍历(递归)

其实这种就是一个类似dfs的思想。用递归实现。前面有很详细的介绍递归算法。我们采用的三序遍历是采用同一个递归。并且大家也都直到递归是一个有来有回的过程。三序遍历只是利用了递归中的来回过程中不同片段截取输出,而达到前(中、后序遍历的结果)。

前序递归

前序的规则就是根结点 ---> 左子树 ---> 右子树.我们在调用递归前进行节点操作。对于前序,就是先访问(输出)该节点。而递归左,递归右侧,会优先递归左侧。直到没有左节点。才会停止。访问次序大致为:
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结

public void qianxu(node t)// 前序递归 前序遍历:根结点 ---> 左子树 ---> 右子树
{
	if (t != null) {
		System.out.print(t.value + " ");// 当前节点
		qianxu(t.left);
		qianxu(t.right);
	}
}

中序递归

有了前序的经验,我们就很好利用递归实现中序遍历。中序遍历的规则是:左子树---> 根结点 ---> 右子树。所以我们访问节点的顺序需要变。

  • 我们直到递归是来回的过程,对于恰好有两个子节点(子节点无节点)的节点来说。只需要访问一次左节点,访问根,访问右节点。即可。
  • 而如果两侧有节点来说。每个节点都要满足中序遍历的规则。我们从根先访问左节点。到了左节点这儿左节点又变成一颗子树也要满足中序遍历要求。所以就要先访问左节点的左节点(如果存在)。那么如果你这样想,规则虽然懂了。但是也太复杂了。那么我们借助递归。因为它的子问题和根节点的问题一致,只是范围减小了。所以我们使用递归思想来解决。
  • 那么递归的逻辑为:考虑特殊情况(特殊就直接访问)不进行递归否则递归的访问左子树(让左子树执行相同函数,特殊就停止递归输出,不特殊就一直找下去直到最左侧节点。)——>输出该节点—>递归的访问右子树.

代码为:

public void zhongxu(node t)// 中序遍历 中序遍历:左子树---> 根结点 ---> 右子树
{
	if (t != null) {
		zhongxu(t.left);
		System.out.print(t.value + " ");// 访问完左节点访问当前节点
		zhongxu(t.right);
	}
}

后序递归

同理,有了前面的分析,后续就是左子树 ---> 右子树 ---> 根结点

public void houxu(node t)// 后序遍历 后序遍历:左子树 ---> 右子树 ---> 根结点
{
	if (t != null) {
		houxu(t.left);
		houxu(t.right);
		System.out.print(t.value + " "); // 访问玩左右访问当前节点
	}
}

非递归前序

法一(技巧)

  • 非递归的前序。我们利用栈的性质替代递归,因为递归有时候在效率方面不是令人满意的。
    利用栈,我们直到栈的顺序为后进先出。那么顺序如何添加?递归是左递归,右递归。但是利用栈要相反,因为如果左进栈、右进栈会出现以下后果:
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    所以,我们要利用递归的思路,需要先放右节点进栈,再放左节点进栈,这个下次·再取节点取到左节点·,这个节点再右节点进栈,左节点进栈。然后循环一直到最后会一直优先取到左节点。达到和递归顺序相仿效果。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    每pop完添加右左节点直接输出(访问)即可完成前序非递归遍历。
public void qianxu3(node t)// 非递归前序 栈 先左后右  t一般为root
{
	Stack<node> q1 = new Stack<node>();
	if (t == null)
		return;
	if (t != null) {
		q1.push(t);
	}
	while (!q1.empty()) {
		node t1 = q1.pop();
		if (t1.right != null) {
			q1.push(t1.right);
		}
		if (t1.left != null) {
			q1.push(t1.left);
		}
		System.out.print(t1.value + " ");
	}
}

法二(传统)

方法二和非递归中序遍历的方法类似,只不过需要修改输出时间,在进栈时候输入访问节点即可。具体参考中序遍历分析。

public void qianxu2(node t) {
		Stack<node> q1 = new Stack();	
		while(!q1.isEmpty()||t!=null)
		{
			if (t!=null) {
				System.out.print(t.value+" ");
				q1.push(t);				
				t=t.left;
			}
			else {
				t=q1.pop();
				t=t.right;
			}
		}
	}

非递归中序

非递归中序和前序有所区别。
我们直到中序排列的顺序是:左节点,根节点,右节点。那么我们在经过根节点的前面节点 不能释放, 因为后面还需要用到它。所以要用栈先储存
它的规则大致为:

  • 依次存入左节点所有点,直到最左侧在栈顶。
  • 开始抛出栈顶并访问。(例如第一个抛出2)。如果有右节点。那么将右节点加入栈中,然后右节点一致左下遍历直到尾部。(这里5和7没有左节点,所以不加)但是如果抛出15。右节点加入23.再找23的左侧节点加入栈顶。就这样循环下去直到栈为空

可行性分析:中序是左—中—右的顺序。访问完左侧。当抛出当前点的时候说明左侧已经访问完(或者自己就是左侧),那么需要首先访问当前点的右侧。那么这个右节点把它当成根节点重复相同操作(因为右节点要满足先左再右的顺序)。这样其实就是模拟了一个递归的过程,需要自己思考。
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
实现代码1:

public void zhongxu2(node t) {
	Stack<node> q1 = new Stack();	
	while(!q1.isEmpty()||t!=null)
	{
		if (t!=null) {
			q1.push(t);
			t=t.left;
		}
		else {
			t=q1.pop();
			System.out.print(t.value+" ");
			t=t.right;
		}
	}
}

实现代码2:(个人首次写的)

public void zhongxu3(node t)// 先储藏所有左侧点,抛出一个点,访问该点右节点,对右节点在储存所有子左节点
{
	Stack<node> q1 = new Stack();
	if (t == null)
		return;
	if (t != null) {
		q1.push(t);
	}
	node t1 = q1.peek();// 不能抛出,要先存最左侧
	while (t1.left != null) {
		t1 = t1.left;
		q1.push(t1);
	}
	while (!q1.isEmpty()) {
		node t2 = q1.pop();
		System.out.print(t2.value + " ");
		if (t2.right != null) {
			t2 = t2.right;
			q1.push(t2);
			while (t2.left != null) {
				t2 = t2.left;
				q1.push(t2);
			}
		}
	}
}

非递归后序※

非递归后序遍历有两种方法
一种方法是利用和前面中序、前序第二种方法类似的方法进入压栈出栈,但是要借助额外的标记次数,一个节点访问第二次才能输出。(这个访问第一次是入栈,第二次是子树解决完毕自己即将出栈(先不出栈))。

法1(传统方法)

在前面的前序和中序先到最左侧压入栈的时候,两种顺序依次是

  • 前序: 中入栈——>左入栈——>左出栈——>中出栈——>右入栈——>右孩子入出——>右出栈 在入栈时候操作即可前序
  • 中序: 中入栈——>左入栈——>左出栈——>中出栈——>右入栈 ——>右孩子入出——>右出栈按照出栈顺序即可完成中序

而在后序遍历中:它有这样的规则:

  • 入栈,第一次访问
  • 即将出栈。第二次访问,
  • 如果有右孩子,先不出栈把右孩子压入栈第一次访问,如果没右孩子。访问从栈中弹出。
  • 循环重复,直到栈为空

数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
实现代码为(用map记录节点出现次数):

public void houxu2(node t) {
	Stack<node> q1 = new Stack();	
	Map<Integer,Integer >map=new HashMap<>();
	while(!q1.isEmpty()||t!=null)
	{
		if (t!=null) {
			q1.push(t);
			map.put(t.value, 1); //t.value标记这个值节点出现的次数
			t=t.left;
		}
		else {
			t=q1.peek();
			if(map.get(t.value)==2) {//第二次访问,抛出
				q1.pop();
				System.out.print(t.value+" ");
				t=null;//需要往上走
			}
			else {
				map.put(t.value, 2);
				t=t.right;
			}
			
		}
	}
}

法2(双栈):

另一种方法是借助双栈进行处理。我们曾在前序方法一借助一个栈右压,左压。持续让达到一个前序遍历的效果。但是这个方法很难实现后续。

  • 分析相同方法,如果我们先压左,再压右,那么我们获得的顺序将是和前序完全相反的顺序(顺序为:中间,右侧,左侧。倒过来刚好是左侧、右侧、中间的后续)对称看起来的前序。即用另一个栈将序列进行反转顺序
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    如果再这个过程,我们利用另一个栈进行储存,将它的首次入栈用一个栈存入,相当于起到一个反转的作用。
    数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结
    实现代码为:
public void houxu3(node t)// q1和q2 q1要先右后左,先遍历右侧,q1先装右侧就把右侧放到前面,左侧放在上面(栈顶)
{
	Stack<node> q1 = new Stack();
	Stack<node> q2 = new Stack();
	if (t == null)
		return;
	if (t != null) {
		q1.push(t);
	}
	while (!q1.isEmpty()) {
		node t1 = q1.pop();
		q2.push(t1);
		if (t1.left != null) {
			q1.push(t1.left);
		}
		if (t1.right != null) {
			q1.push(t1.right);
		}
	}
	while (!q2.isEmpty()) {
		node t1 = q2.pop();
		System.out.print(t1.value + " ");
	}
}

总结

测试结果:
数据结构和算法-二叉树的遍历
二叉树的前中后和层序遍历详细图解(递归和非递归写法)
java实现二叉树的遍历(递归和非递归)
二叉树——前序遍历、中序遍历、后序遍历、层序遍历详解(递归非递归)
前言
层序遍历
前中后序遍历(递归)
非递归前序
非递归中序
非递归后序※
总结