leetcode算法练习——二叉树理论基础 144.145.94.递归遍历 迭代遍历

leetcode算法练习

二叉树理论基础

二叉树的种类:

  1. 满二叉树——深度从1开始 总结点数为(2^k)-1
  2. 完全二叉树:除了底层 其他层都是满的(从左到右 从上到下编号连续才可以)
  3. 二叉搜索树:结点便于搜索 一定有顺序(左孩子小于父结点 右孩子大于父结点) 搜索一个结点的时间复杂度是logn级别的
  4. 平衡二叉搜索树(AVL树):符合二叉平衡树的要求且左子树和右子树高度的绝对值不能超过1

二叉树的存储方式:
链式存储:利用左右指针
线性存储:利用数组

找左孩子:2 * i + 1
找右孩子:2 * i + 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;
  	}
}

144.145.94.递归遍历

题目链接
题目链接
题目链接

递归三部曲:

  1. 确定递归函数的参数和返回值(不用一次性确定 可以边写边加 一般是一个根结点 一个数组/返回值一般是 void)
  2. 确定终止条件
  3. 确定单层递归的逻辑
// 前序遍历·递归·LC144_二叉树的前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<Integer>();
        preorder(root, result);
        return result;
    }
	// 参数传入根结点和数组 数组里存放前序遍历结果
    public void preorder(TreeNode root, List<Integer> result) {
    	// 遇到空结点 就返回
        if (root == null) {
            return;
        }
        // 数组放遍历的元素 就是根
        result.add(root.val);
        // 左
        preorder(root.left, result);
        // 右
        preorder(root.right, result);
    }
}
// 中序遍历·递归·LC94_二叉树的中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        inorder(root, res);
        return res;
    }

    void inorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        inorder(root.left, list);
        list.add(root.val);             // 注意这一句
        inorder(root.right, list);
    }
}
// 后序遍历·递归·LC145_二叉树的后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        postorder(root, res);
        return res;
    }

    void postorder(TreeNode root, List<Integer> list) {
        if (root == null) {
            return;
        }
        postorder(root.left, list);
        postorder(root.right, list);
        list.add(root.val);             // 注意这一句
    }
}

迭代遍历

也是使用栈这种数据结构来实现
迭代法

// 前序遍历顺序:根-左-右,入栈顺序:根-右-左
class Solution {
	// 传入根结点
    public List<Integer> preorderTraversal(TreeNode root) {
        // 存放遍历结果
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        // 定义一个栈 存放的元素是TreeNode 一般是个结构体 包含value 左右指针
        Stack<TreeNode> stack = new Stack<>();
        // 根结点入栈
        stack.push(root);
        // 循环处理栈 栈不为空就一直执行
        while (!stack.isEmpty()){
            // 先把放入的根结点 
            TreeNode node = stack.pop();
            // 弹出存进数组
            result.add(node.val);
            // 先入右孩子 再入左孩子
            if (node.right != null){
                stack.push(node.right);
            }
            if (node.left != null){
                stack.push(node.left);
            }
        }
        return result;
    }
}

// 中序遍历看下一部分代码

// 可以将前序的代码 左右颠倒一下 则变为根右左 再将数组反转 变为 左右根
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()){
            TreeNode node = stack.pop();
            result.add(node.val);
            if (node.left != null){
                stack.push(node.left);
            }
            if (node.right != null){
                stack.push(node.right);
            }
        }
        // 反转数组
        Collections.reverse(result);
        return result;
    }
}

中序迭代

前序遍历:遍历顺序和处理顺序(数字放入数组的顺序)是一致的:54126
中序遍历:访问肯定是从根结点5开始 但是遍历是从1开始的 ——故遍历顺序和处理顺序不一致:14256

如何处理中序?

  • 利用一个指针 将对应的元素存入数组
  • 在遍历的时候要用栈来记录我们遍历过的顺序 因为处理元素的时候 其实是按照遍历的顺序逆向输出的
  1. 先访问5 存入栈——栈内5
  2. 存入4——栈内5 4
  3. 存入1——栈内5 4 1
  4. 一路向左访问 弹出1 加入数组——栈内5 4 数组内1
  5. 这时候1的右 也是空 所以访问栈内的元素 弹出4——栈内5 数组内1 4
  6. 4的右孩子是2 2入栈——栈内5 2 2的左 2弹出——栈内5 数组内1 4 2
  7. 2的右也为空 5弹出——栈内为空 数组内1 4 2 5
  8. 5的右孩子不为空 3加入栈——栈内6
  9. 6的左孩子为空 弹出——数组内1 4 2 5 6
    10.6的右孩子为空 栈内也为空 结束

总结:
先一路向左 到没有左孩子为止 在弹出栈内元素 看该元素的右孩子是不是也为空 没有就再接着弹出 有就入栈

// 中序遍历和处理顺序不一样 不能直接修改前序的代码
// 中序遍历顺序: 左-根-右 入栈顺序: 左-右
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        // 结果集
        List<Integer> result = new ArrayList<>();
        if (root == null){
            return result;
        }
        Stack<TreeNode> stack = new Stack<>();
        // 指针
        TreeNode cur = root;
        // 指针为空 栈也为空 就终止遍历
        while (cur != null || !stack.isEmpty()){
            // 指针不为空 就加入该元素 指针往左走 一路向左
            if (cur != null){
               stack.push(cur);
               cur = cur.left;
            }else{
                // 如果指针为空 栈里弹出加入数组
                cur = stack.pop();
                result.add(cur.val);
                // 指针右移
                cur = cur.right;
           }
        }
        return result;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值