入门数据结构JAVADS——如何通过遍历顺序构建二叉树

目录

前言

构建二叉树的前提:

为什么需要两个不同类型的遍历:

前序遍历 + 中序遍历 

 我们的算法思路如下:

举例:

 代码实现

后序遍历 + 中序遍历

结尾


前言

入门数据结构JAVA DS——二叉树的介绍 (构建,性质,基本操作等) (1)-CSDN博客

在上一篇博客中,笔者讲过,如果告诉你遍历顺序,是可以通过算法构建出二叉树的,这篇博客就是补充,笔者想具体的阐述一下 "告诉遍历顺序从而构建二叉树" 算法.

构建二叉树的前提:

要唯一地构建出一棵二叉树,至少需要两个不同类型的遍历结果。最常见的组合是:

  1. 前序遍历 + 中序遍历 
  2. 后序遍历 + 中序遍历

只用一种遍历顺序无法唯一地确定一棵二叉树,因为同样的遍历顺序可能对应多个不同的二叉树。下面详细说明这些组合是如何帮助构建二叉树的。

为什么需要两个不同类型的遍历:

  • 中序遍历:中序遍历能够提供节点在二叉树中相对于左右子树的相对位置,但无法告诉你树的层次结构(即节点的父子关系)。例如,如果只知道中序遍历 D B E A F C,我们只能得知顺序关系,但无法区分每个节点的父节点和子节点。

  • 前序遍历 :前序遍历能够提供根节点和子树的先后顺序。它告诉我们哪个节点是根节点,但无法精确地告诉我们左子树和右子树的细节。

  • 后序遍历 :后序遍历提供了叶子节点的顺序以及根节点的最终位置,但同样无法给出左右子树的确切划分。

通俗地说:

  • 中序遍历告诉我们每个节点的左右子树。
  • 前序遍历后序遍历帮助我们找到子根节点。

举个例子:

如果有前序遍历 A B D E C F 和中序遍历 D B E A F C,通过中序遍历可以知道:

  • A 是根节点(因为前序遍历的第一个是根节点)。
  • 在中序遍历中,A 左边的是 D B E,所以这是左子树;A 右边的是 F C,所以这是右子树。

当然,如果告诉你那些位置是空的话,只要一种遍历就够了,可以参考

入门数据结构JAVADS——部分常见的二叉树OJ题目(1) 持续更新-CSDN博客  第四题

前序遍历 + 中序遍历 

笔者通过题目来讲解

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

 我们的算法思路如下:

  1. 确定根节点

    • 前序遍历的第一个元素一定是根节点。根据这个根节点,我们可以在中序遍历中找到根节点的位置,从而确定左右子树。
  2. 划分左右子树

    • 在中序遍历中,根节点左边的元素都是左子树的节点,右边的元素都是右子树的节点。
    • 根据中序遍历的左右划分,再利用前序遍历来确定左右子树的结构。
  3. 递归处理左右子树

    • 对左子树和右子树分别递归重复上述步骤,直到构建完成。

举例:

我们有如下的前序遍历和中序遍历:

  • 前序遍历[A, B, D, E, C, F]
  • 中序遍历[D, B, E, A, F, C]

步骤 1:确定根节点

  • 根据前序遍历的第一个元素,A 是根节点。
  • 在中序遍历中找到 A,可以划分为:
    • 左子树的中序遍历:[D, B, E]
    • 右子树的中序遍历:[F, C]

步骤 2:处理左子树

  • 前序遍历的第二个元素 B 是左子树的根节点。
  • 在左子树的中序遍历 [D, B, E] 中找到 B,可以划分为:
    • 左子树的左子树的中序遍历:[D]
    • 左子树的右子树的中序遍历:[E]

步骤 3:处理右子树

  • 前序遍历中,接下来的是 C,这是右子树的根节点。
  • 在右子树的中序遍历 [F, C] 中找到 C,可以划分为:
    • 左子树的中序遍历:[F](即 C 的左子树)
    • 没有右子树。

   这样处理下来,最后的结构是

       A
      / \
     B   C
    / \  /
   D   E F

 代码实现

我们用这道力扣上的题目来做代码实现

代码如下

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int preindex=0;
    public TreeNode buildTree(int[] preorder, int[] inorder) 
    {
          return buildTreeChilde(preorder,inorder,0,inorder.length-1);
    }
    // 大体思路,通过中序找子树,通过前序构建每个结点.
      public TreeNode buildTreeChilde(int[] preorder, int[] inorder,int inbegin,int inend)
      // begin 和 end 负责检查是否还有结点
    {
        if(inbegin>inend)
        {
            return null ;
        }
        TreeNode root = new TreeNode(preorder[preindex]); 
        int rootidx = find(inorder,inbegin,inend,preorder[preindex]);
        preindex++;
        root.left = buildTreeChilde( preorder,  inorder, inbegin, rootidx-1);
        root.right = buildTreeChilde( preorder,  inorder, rootidx+1, inend);
        return root;
    }
    private int find(int [] inorder,int begin,int end,int key)
    {
        
        for(int i=0;i<inorder.length;i++)
        {
            if(inorder[i]==key)
            {
                    return i;
            }
        }
        return -1;
    }
}

 可以看到,我们设置了 inbegin , inend,rootidx 这三个变量,它们有什么用呢? 

rootidx很简单,每个前序遍历的结点都在中序遍历中有对应位置,rootidx负责定位它,从而将左右子树分割开.

inbegin , inend呢?它们负责确定(子)根结点的左右孩子结点是否是空结点,我们可以看到,每次分割完成以后,对于左右子树来说,如果 inbegin<=inend ,说明还存在结点.如果 inbegin>inend , 那说明已经没有结点了,这很好理解,中序遍历表中  inbegin 已经大于 inend 了, 已经没有结点了.

至于为什么先左后右,则是通过前序遍历的顺序得到的.

通过我们这样的递归,最后就可以构建出二叉树

后序遍历 + 中序遍历

 106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

 也差不多是同理,我们如果倒着去看后序遍历表,就可以发现,差不多是  根 -- 右 -- 左 版本的前序遍历,只要稍微改一下代码就好了

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    // 左 右 根
    public int postordernum=0;
    public TreeNode buildTree(int[] inorder, int[] postorder)
    {
        postordernum=postorder.length-1;
        return buildTreeChild(inorder,postorder,0,inorder.length-1);
    }
          public TreeNode buildTreeChild(int[] inorder, int[] postorder,int inbegin,int inend)
          {
             if(inbegin>inend)
             {
                return null;
             }
             TreeNode root=new TreeNode(postorder[postordernum]);
             int rootindex=find(inorder,inbegin,inend,postorder[postordernum]);
             postordernum--;
             root.right=buildTreeChild(inorder,postorder,rootindex+1,inend);
             root.left=buildTreeChild(inorder,postorder,inbegin,rootindex-1);
             return root;
          }
          private int find(int [] inorder,int inbegin,int inend,int key)
          {
            for(int i=inbegin;i<=inend;i++)
            {
                if(inorder[i]==key)
                {
                    return i;
                }
            }
            return -1;
          }
}

 从结尾开始反向遍历后序遍历表,然后去构建, 思路基本是一致的.

结尾

笔者是懒狗,创作不易,知识简单,但毕竟用爱发电!

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值