代码随想录算法训练营第14天|| 二叉树理论基础 || 递归遍历 || 迭代遍历 || 统一迭代

代码随想录算法训练营第14天|| 二叉树理论基础 || 递归遍历 || 迭代遍历 || 统一迭代

二叉树理论基础

二叉树的种类

  • 满二叉树:只有度为0和度为2的结点,且度为0的结点都在同一层上
  • 完全二叉树:只有最底层可能没满(但仍保持从左到右的顺序),其它层均满
  • 二叉搜索树:左子树的结点均小于根节点,右子树的结点均大于根节点
  • 平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1

二叉树的存储方式

  • 顺序存储(使用数组)

    • 内存连续分布
    • 父节点下标为i,左孩子就是2i+1,右孩子就是2i+2
  • 链式存储(使用链表)

    • 包含结点元素、左指针、右指针
    • 通过指针将分布在各个地址的结点串起来

二叉树的遍历方式

深度优先遍历:向往深处走,遇到叶子结点再往回走

广度优先遍历:一层一层去遍历

  • 深度优先
    • 前序遍历
    • 中序遍历
    • 后序遍历
  • 广度优先
    • 层次遍历(迭代法)

栈就是递归的一种实现结构,其实前中后序遍历都可转化成使用栈实现非递归方式实现

二叉树的定义

顺序结构:直接使用数组

链式结构:关键得定义结点

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;
  	}
}

二叉树的递归遍历

递归方法论:三要素

  1. 确定函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑

以前序遍历为例:public List<Integer> preorderTraversal(TreeNode root) {

  1. 确定参数和返回值:因为需要返回一个List集合,且我们要递归遍历结点,所以参数为List和TreeNode,至于返回值,可以为null,也可以为List
  2. 确定终止条件:这里理所当然遍历到null就直接返回
  3. 确定单层递归的逻辑:由于是先序遍历,所以我们需要先存储根节点,再去遍历左结点,最后遍历右节点。
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        preTraver(root, result);
        return result;
    }

    public void preTraver(TreeNode node, List<Integer> result) {
        if (node == null)
            return;
        result.add(node.val);
        preTraver(node.left, result);
        preTraver(node.right, result);
    }
}

后序与中序只需调整一下代码顺序即可

二叉树的迭代遍历

递归的调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,递归返回时弹出

前序遍历

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.add(root);
        while (!stack.empty()) {
            TreeNode node = stack.pop();
            if (node == null)
                continue;
            result.add(node.val);
            stack.add(node.right);
            stack.add(node.left);
        }
        return result;
    }
}

后序遍历

  • 只需修改一下代码顺序即可
  • 先序遍历:中左右 --修改左右孩子入栈顺序–> 中右左 – 颠倒整体顺序 --> 左右中 即为后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        stack.add(root);
        while (!stack.empty()) {
            TreeNode node = stack.pop();
            if (node == null)
                continue;
            result.add(node.val);
            stack.add(node.left);
            stack.add(node.right);
        }
        Collections.reverse(result);
        return result;
    }
}

中序遍历:

  • 中序遍历和前序遍历区别在于,前序遍历是访问元素的顺序与处理元素的顺序是一致的
  • 后序遍历、中序遍历访问、处理元素顺序并不是一致,但后序遍历可通过调整先序遍历代码顺序得到,中序则不行
  • 我们都知道我们的访问顺序肯定是从中开始的,而中序遍历确是左孩子开始,所以我们先一路向左,到头就处理,然后访问右孩子。
  • 中序遍历(左中右)的流程
    • 一路向左,为null时就弹出栈顶并处理,然后访问其右孩子(接着一路向左的操作),
    • 假设其右孩子为null,继续弹出并处理,然后访问其右孩子
    • 循环1/2步骤即可,直到指针和栈均为null
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        TreeNode node = root;
        while (node != null || !stack.empty()) {
            if (node != null) {
                stack.add(node);
                node = node.left;
            } else {
                node = stack.pop();
                result.add(node.val);
                node = node.right;
            }
        }
        return result;
    }
}

二叉树的统一迭代法

在此前我们实现的迭代法中,除了先序和后序有关联,中序完全是另一个风格,栈和指针混用

之前我们使用栈,无法同时解决访问结点和处理结点不一致的情况,那我们就将访问的结点入栈,把要处理的结点入栈并且做标记

这里的标记则使用一个null指针作为标记,这种方法叫做标记法

以先序为例:

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<TreeNode>();
        if (root != null)
            stack.add(root);
        while (!stack.empty()) {
            TreeNode node = stack.pop();
            if (node != null) {
                if (node.right != null)
                    stack.add(node.right);
                if (node.left != null)
                    stack.add(node.left);
                stack.add(node);
                stack.add(null);
            } else {
                node = stack.pop();
                result.add(node.val);
            }
        }
        return result;
    }
}

中序和后序,只需要将

stack.add(node); stack.add(null);改变位置即可

本质就是标记一下处理结点,通过改变放入结点及其左右孩子的顺序,有点类似递归,做了标记的当前结点,但是如果左右孩子后入栈,就得先处理左右孩子最后才能到达当前结点,这就是后序遍历。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值