树之习题分析上——树的遍历

一、树的前序遍历

(一)、题目需求

​ 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

示例 1:

img

输入:root = [1,null,2,3]
输出:[1,2,3]
​```

示例 2:

输入:root = []
输出:[]

示例 3:

输入:root = [1]
输出:[1]

示例 4:

img

输入:root = [1,2]
输出:[1,2]

示例 5:

img

输入:root = [1,null,2]
输出:[1,2]

提示:

  • 树中节点数目在范围 [0, 100]
  • -100 <= Node.val <= 100

(二)、递归解法

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    List<Integer> left = preorderTraversal(root.left);
    List<Integer> right = preorderTraversal(root.right);

    result.add(root.val);
    result.addAll(left);
    result.addAll(right);

    return result;
}

(三)、非递归解法

public List<Integer> preorderTraversalByStack(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    LinkedList<TreeNode> stack = new LinkedList();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode treeNode = stack.pop();
        result.add(treeNode.val);
        if (treeNode.right != null) {
            stack.push(treeNode.right);
        }
        if (treeNode.left != null) {
            stack.push(treeNode.left);
        }
    }
    return result;
}

(四)、代码分析

1、递归解法分析

​ 递归解法相对于非递归解法而言较为简单。本质上是将一颗树划分为3个部分:根节点、左半部分、右半部分。对于每一个结点皆如此进行递归划分,最终在划分结束后,按照前序遍历的规则:先访问根结点、再访问根的左结点、最后是访问根的右结点的顺序进行添加进结果集中。

2、非递归解法分析

1、利用栈的先进后出的特性,进行树的非递归前序遍历;并将根节点push()进入栈中。

LinkedList<TreeNode> stack = new LinkedList();
stack.push(root);

2、由于树的前序遍历的规则为:先访问根结点、再访问根的左结点、最后是访问根的右结点。同时栈的特性是先进后出,因此需要先判断右结点是否为空,若不为空则加入栈,再判断左结点是否为空,若不为空则加入栈。在有新的结点进入栈后,再不断将其推出栈,进行其左右子结点的判断。

while (!stack.isEmpty()) {
    TreeNode treeNode = stack.pop();
    result.add(treeNode.val);
    if (treeNode.right != null) {
        stack.push(treeNode.right);
    }
    if (treeNode.left != null) {
        stack.push(treeNode.left);
    }
}

(五)、流程图分析

1、递归流程图

1、初始状态
在这里插入图片描述
2、将其分为根结点和左右两个部分
在这里插入图片描述
3、遍历“1”的左结点,同时将其分为3个部分

在这里插入图片描述
4、遍历“2”结点的左结点,同时将其分为3个部分。“4”结点无左右子结点
在这里插入图片描述

5、遍历“2”结点的右结点,同时将其分为3个部分。“5”结点无左右子结点
在这里插入图片描述
6、“2”结点遍历结束,得出2结点的结果集
在这里插入图片描述
7、遍历“1”结点的右节点,并将其分为3个部分
在这里插入图片描述
8、遍历“3”结点的左结点,并将其分为3个部分。“6”结点无左右子结点
在这里插入图片描述
9、“3”结点遍历结束,得出其结果集
在这里插入图片描述
10、遍历结束,得到“1”结点对应的结果集
在这里插入图片描述

2、非递归流程图

1、初始状态
在这里插入图片描述
2、根节点入栈
在这里插入图片描述
3、“1”结点的左右子节点进入栈内,同时“1”结点进入结果集
在这里插入图片描述
4、“2”结点出栈,其左右子节点入栈,同时“2”节点进入结果集
在这里插入图片描述
5、“4”节点无左右子节点,因此“4”节点直接出栈,同时“4”节点进入结果集
在这里插入图片描述
6、“5”节点无左右子节点,因此“5”节点直接出栈,同时“5”节点进入结果集
在这里插入图片描述
7、“3”节点出栈,同时“3”节点的左结点进栈,“3”节点进入结果集
在这里插入图片描述
8、“6”节点无左右子节点,因此“6”节点直接出栈,同时“6”节点进入结果集。遍历结束
在这里插入图片描述

二、树的中序遍历

(一)、题目需求

给定一个二叉树,返回它的中序 遍历。

示例:

输入: [1,null,2,3]
   1
    \
     2
    /
   3

输出: [1,3,2]

进阶: 递归算法很简单,你可以通过迭代算法完成吗?

(二)、递归解法

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    List<Integer> left = inorderTraversal(root.left);
    List<Integer> right = inorderTraversal(root.right);

    result.addAll(left);
    result.add(root.val);
    result.addAll(right);

    return result;
}

(三)、非递归解法

public List<Integer> inorderTraversalByStack(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    LinkedList<TreeNode> stack = new LinkedList<>();
    TreeNode current = root;
    while (current != null || !stack.isEmpty()) {
        while (current != null) {
            stack.push(current);
            current = current.left;
        }
        current = stack.pop();
        result.add(current.val);
        current = current.right;
    }
    return result;
}

(四)、代码分析

  • 由于中序遍历的递归解法与前序遍历的相差不大,因此可参考前序遍历

1、首先将cuurent定位至root节点的最左边节点

while (current != null) {
    stack.push(current);
    current = current.left;
}

2、将current推出栈,并将其加入结果集中,将current定位至其右子节点处,若current的右子节点不为空,则意味着current节点的下方仍有节点未被遍历,则重复上面步骤1。若current的右子节点为空,则继续遍历栈中的剩余节点。

current = stack.pop();
result.add(current.val);
current = current.right;

(五)、流程图分析

  • 由于中序遍历的流程图分析与前序遍历的相差不大,因此可参考前序遍历
    1、初始状态
    在这里插入图片描述
    2、设立结果集、栈、current指针
    在这里插入图片描述
    3、current遍历至root节点的最左节点,同时逐渐入栈
    在这里插入图片描述
    4、“4”节点出栈,同时“4”节点加入结果集合,由于“4”节点的右子节点为空,所以current为空
    在这里插入图片描述
    5、“2”节点出栈,同时“2”节点加入结果集合,由于“2‘节点的右子节点非空,因此current指向其右子节点
    在这里插入图片描述
    6、current非空,因此”5“节点入栈
    在这里插入图片描述
    7、”5“节点出栈,同时“5”节点加入结果集合
    在这里插入图片描述
    8、“1”节点出栈,同时“1”节点加入结果集合,由于“1‘节点的右子节点非空,因此current指向其右子节点
    在这里插入图片描述

9、current指向”3“节点的最左边,同时逐渐入栈
在这里插入图片描述

10、”6“节点出栈,同时”6“节点加入结果集中,current为空
在这里插入图片描述

11、”3“节点出栈,”3“节点进入结果集中。由于current和栈均为空,因此退出循环
在这里插入图片描述

三、树的后序遍历

(一)、题目需求

给定一个二叉树,返回它的 后序 遍历。

示例:

输入: [1,null,2,3]  
   1
    \
     2
    /
   3 

输出: [3,2,1]

(二)、递归解法

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    List<Integer> left = postorderTraversal(root.left);
    List<Integer> right = postorderTraversal(root.right);

    result.addAll(left);
    result.addAll(right);
    result.add(root.val);
    return result;
}

(三)、非递归解法——破坏原有数据结构

public List<Integer> postorderTraversalByStack(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    LinkedList<TreeNode> stack = new LinkedList<>();
    stack.push(root);
    while (!stack.isEmpty()) {
        TreeNode treeNode = stack.peek();
        if (treeNode.right == null && treeNode.left == null) {
            result.add(stack.pop().val);
        }
        if (treeNode.right != null) {
            stack.push(treeNode.right);
            treeNode.right = null;
        }
        if (treeNode.left != null) {
            stack.push(treeNode.left);
            treeNode.left = null;
        }
    }
    return result;
}

(四)、非递归解法——无破坏原有数据结构

public List<Integer> postorderTraversalByStack2(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if (root == null) {
        return result;
    }

    LinkedList<TreeNode> stack = new LinkedList<>();
    TreeNode current = root;
    TreeNode pre = null;

    stack.push(root);
    while (!stack.isEmpty()) {
        current = stack.peek();
        if (pre == null || pre.left == current || pre.right == current) {
            if (current.left != null) {
                stack.push(current.left);
            } else if (current.right != null) {
                stack.push(current.right);
            }
        } else if (current.left == pre) {
            if (current.right != null) {
                stack.push(current.right);
            }
        } else {
            result.add(current.val);
            stack.pop();
        }
        pre = current;
    }
    return result;
}

(五)、代码分析

  • 由于后序遍历的递归解法与前序遍历的相差不大,因此可参考前序遍历
1、非递归解法——破坏原有数据结构

1、创建栈,利用栈的先进后出的特性,遍历树。同时将根结点入栈。

LinkedList<TreeNode> stack = new LinkedList<>();
stack.push(root);

2、由于栈具备先进后出的特性。同时树的后序遍历顺序为:先根的左子节点,再根的右子节点,最后为根节点。因此先判断节点的右子节点是否为空,若不为空,则将其入栈,同时切断两者的联系。再判断节点的左子节点是否为空,若不为空,则将其入栈,同时切断两者的联系。

if (treeNode.right != null) {
    stack.push(treeNode.right);
    treeNode.right = null;
}
if (treeNode.left != null) {
    stack.push(treeNode.left);
    treeNode.left = null;
}

3、首先获取栈顶元素,但栈顶元素不出栈。

若该节点的左右子节点皆为空,则说明该节点为叶子节点或该节点左右子节点皆曾经入过栈。

若该节点为叶子节点,则说明其无左右子节点需遍历,因此直接将其出栈并且将其加入结果集。

若该节点的节点的左右节点皆曾经入过栈,由于栈的特性为先进后出,则说明如今其左右子节点已经出栈,即左子节点与右子节点已进入结果集,因此直接将其出栈并将其加入结果集。

TreeNode treeNode = stack.peek();
if (treeNode.right == null && treeNode.left == null) {
    result.add(stack.pop().val);
}
2、非递归解法——无破坏原有数据结构

1、设置栈、current指针、pre指针

LinkedList<TreeNode> stack = new LinkedList<>();
TreeNode current = root;
TreeNode pre = null;

2、假如pre指针为空,则说明current此时为root节点,其左右子树仍未遍历。

假如pre指针的左子节点或右子节点为current,则说明此时以current为根节点的子树,仍未遍历。

由于后序遍历的顺序为:先左、后右、最后根节点;因此在确定current的左右半边的子树未遍历时,首先判断其左子节点是否为空,若不为空,则先遍历其左半边子树。若左子节点为空,说明其无左子树,进而判断其右子节点是否为空,若不为空,则遍历其右半边子树。

if (pre == null || pre.left == current || pre.right == current) {
    if (current.left != null) {
        stack.push(current.left);
    } else if (current.right != null) {
        stack.push(current.right);
    }
}

3、当current节点的左子节点为pre节点时,说明current节点的左半边子树皆已遍历结束,此时需要遍历current的右半边子树。因此假如current的右子节点非空,则开始将其加入栈中,接下来便重复步骤2,开始逐渐遍历current节点的右半子树。

else if (current.left == pre) {
    if (current.right != null) {
        stack.push(current.right);
    }
} 

4、此时说明pre节点等于current节点或者是current的右子节点为pre节点。

若pre节点等于current节点:说明current节点无左右子节点,此时pre节点才会下移至current处,因此可直接将current节点此时对应的值出栈,并加入结果集中。

若pre节点等于current节点的右子节点:说明current的左右子节点皆已遍历结束;根据栈的先进后出的特性,此时current节点的左右子节点皆已出栈且进入结果集中;而后序遍历的顺序为先左、后右、最后为根节点,因此此时可直接将current节点此时对应的值出栈,并加入结果集中。

else {
    result.add(current.val);
    stack.pop();
}

(六)、流程图分析

1、非递归解法——破坏原有数据结构

1、初始状态
在这里插入图片描述
2、根节点入栈
在这里插入图片描述
3、“1”节点的右节点非空,将“3”节点入栈,同时切断两者的关系
在这里插入图片描述
4、“1”节点的左节点非空,将“2”节点入栈,同时切断两者的关系
在这里插入图片描述
5、“2”节点的右节点非空,将“5”节点入栈,同时切断两者的关系
在这里插入图片描述
6、“2”节点的左节点非空,将“4”节点入栈,同时切断两者的关系
在这里插入图片描述
7、“4”节点的左右节点皆为空,因此直接将“4”节点出栈,并将“4”节点加入结果集中
在这里插入图片描述
8、“5”节点的左右节点皆为空,因此直接将“5”节点出栈,并将“5”节点加入结果集中

在这里插入图片描述
9、“2”节点的左右节点皆为空,因此直接将“2”节点出栈,并将“2”节点加入结果集中
在这里插入图片描述
10、“3”节点的左节点非空,因此将“6”节点入栈,并切断两者的关系
在这里插入图片描述
11、“6”节点的左右节点皆为空,因此直接将“6”节点出栈,并将“6”节点加入结果集中
在这里插入图片描述
12、“3”节点的左右节点皆为空,因此直接将“3”节点出栈,并将“3”节点加入结果集中
在这里插入图片描述
13、“1”节点的左右节点皆为空,因此直接将“1”节点出栈,并将“1”节点加入结果集中,遍历结束。
在这里插入图片描述

2、非递归解法——无破坏原有数据结构

1、初始状态
在这里插入图片描述
2、设置current、pre、栈、结果集,并将root节点入栈
在这里插入图片描述
3、pre为空,“1”节点的左子节点非空,因此“2“节点入栈;同时pre移至current指针的位置、current移至其左子节点处即”1“节点的左子节点”2“节点处。
在这里插入图片描述
4、pre的左子节点为current,“2”节点的左子节点非空,因此“4“节点入栈;同时pre移至current指针的位置、current移至其左子节点处即”2“节点的左子节点”4“节点处。
在这里插入图片描述
5、pre的左子节点为current,“4”节点的左右子节点皆为空,因此无新的节点入栈;同时pre移至current指针的位置。此时pre指针与current指针指向同一节点。
在这里插入图片描述
6、由于pre指针与current指针指向同一节点,即”4“节点。因此”4“节点出栈,并加入结果集。同时current节点指向栈顶节点即”2“节点。此时pre指针指向节点为current指针指向的节点的左子节点——因此此时需判断current节点的右子节点是否为空。
在这里插入图片描述
7、由于”2“节点的右子节点非空,因此”5“节点入栈。同时pre指针移至current指针处,current指针移至新的栈顶元素节点处即”5“节点。
在这里插入图片描述
8、pre的右子节点为current,“5”节点的左右子节点皆为空,因此无新的节点入栈;同时pre移至current指针的位置。此时pre指针与current指针指向同一节点。
在这里插入图片描述
9、由于pre指针与current指针指向同一节点,即”6“节点。因此”6“节点出栈,并加入结果集。同时current节点指向栈顶节点即”2“节点。此时pre指针指向节点为current指针指向的节点的右子节点——因此此时可判断得出以”2“节点的根节点的左右子树皆遍历完成,因此可将”2“节点出栈、加入结果集。
在这里插入图片描述
10、”2“节点出栈、加入结果集。pre指针移至current指针处,current指针移至栈顶元素节点”1“节点。此时pre指针指向节点为current指针指向的节点的左子节点——因此此时需判断current节点的右子节点是否为空。
在这里插入图片描述
11、"1"节点的右子节点非空,因此”3“节点入栈。同时pre指针移至current指针处,current指针移至栈顶元素”3“节点处。
在这里插入图片描述
12、pre的右子节点为current,“3”节点的左子节点非空,因此”6“节点入栈;同时pre移至current指针的位置,current指针移至栈顶元素”6“节点处。
在这里插入图片描述
13、pre的左子节点为current,“6”节点的左右子节点皆为空,因此无新的节点入栈;同时pre移至current指针的位置。此时pre指针与current指针指向同一节点。
在这里插入图片描述
14、”6“节点出栈,并加入结果集。同时current指针移至栈顶元素”3“节点
在这里插入图片描述
15、由于”3“节点的右子节点为空,所以以”3“节点为根节点的左右子树皆遍历完成。”3“节点出栈,并加入结果集。
在这里插入图片描述
16、pre和current指针指向同一节点,”1“节点出栈,并加入结果集。遍历结束。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值