【算法-二叉树】

#二叉树基础

一. 种类

1.满二叉树

满二叉树
如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树.

2.完全二叉树

完全二叉树
底部从左到右连续, 【堆】就是一个完全二叉树

3.二叉搜索树

二叉搜索树
元素有顺序

4.平衡二叉搜索树

平衡二叉搜索树
左子树和右子树高度的绝对值不能超过1

二. 存储方式

1.线式存储

线式存储
左子节点 2i + 1
右子节点 2
i + 2

2.链式存储

链式存储

三. 遍历方式

1.深度优先

前序遍历(递归法,迭代法)(中左右)
中序遍历(递归法,迭代法)(左中右)
后序遍历(递归法,迭代法)(左右中)

1.广度优先

层次遍历(迭代法)

四. 二叉树的定义

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.二叉树的前序遍历

144.二叉树的前序遍历

145.二叉树的后序遍历

145.二叉树的后序遍历

94.二叉树的中序遍历

94.二叉树的中序遍历
深度优先

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

递归-前序遍历

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    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);
}

递归-中序遍历

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

递归-后序遍历

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);
}
思路: 迭代法

迭代法-前序遍历

迭代法-中序遍历

迭代法-后序遍历

二. 层序遍历

102.二叉树的层序遍历

102. 二叉树的层序遍历
102. 二叉树的层序遍历

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

思路:
如图可知, 二叉树中两个的左右子节点无法感知对方的存在, 所以需要额外的容器去收集关联. 此处利用队列先进先出的特性收集遍历每一层的节点.
我们以根节点为入口, 获取其所有的子节点, 加入队列中, 由此我们可知当前队列的长度为2. 则又在遍历队列的过程中一边数据出队, 一边收集出队节点的子项进入队列, 由于我们的知之前队列的长度为2, 则2耗尽时, 第二层节点刚好遍历完.
由此开始第三层节点的遍历, 依次类推.

代码实现:

public List<List<Integer>> levelOrder(TreeNode 
    if (root == null) {
        return new ArrayList<>();
    }
    // 结果集
    List<List<Integer>> res = new ArrayList<>()
    // 利用队列先进先出的特性
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        // 当前层节点数量
        int size = queue.size();
        // 收集每层节点
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            // 出队 先进先出
            TreeNode poll = queue.poll();
            // 收集该节点的子节点到队列中
            assert poll != null;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
            list.add(poll.val);
        }
        res.add(list);
    }
    return res;
}

107.二叉树的层序遍历 II

107. 二叉树的层序遍历 II
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

思路:
实现方式与102一致, 区别在于在数据收集完毕后, 对结果集进行倒序, 或者在收集时倒序收集.

代码实现:

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    if (root == null) {
        return new ArrayList<>();
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    List<List<Integer>> res = new ArrayList<>();
    while (!queue.isEmpty()) {
        List<Integer> list = new ArrayList<>();
        int size = queue.size();
        while (size-- > 0) {
            // 出队 先进先出
            TreeNode poll = queue.poll();
            assert poll != null;
            if (poll.right != null) {
                queue.offer(poll.right);
            }
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            list.add(poll.val);
            // 倒序收集
            // list.add(0, poll.val);
        }
        res.add(list);
    }
    // 反转结果集
    List<List<Integer>> reverse = new ArrayList<>();
    for (int i = res.size() - 1; i >= 0; i--) {
        reverse.add(res.get(i));
    }
    return reverse;
}

199.二叉树的右视图

199. 二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

思路:
实现方式与102类似, 区别于收集数据的方式, 仅在每层数据的最后一位才将数据加入结果集.

代码实现:

public List<Integer> rightSideView(TreeNode root) {
    if (root == null) {
        return new ArrayList<>(0);
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    List<Integer> res = new ArrayList<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        // 每层节点数
        int size = queue.size();
        // 遍历该层节点
        for (int i = 0; i < size; i++) {
            // 出队
            TreeNode poll = queue.poll();
            assert poll != null;
            // 左右子节点入队
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
            // 最后一个节点
            if (i == size - 1) {
                res.add(poll.val);
            }
        }
    }
    return res;
}

637.二叉树的层平均值

637. 二叉树的层平均值
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

思路:
实现方式与102类似, 区别在于收集数据的内容, 在遍历层节点时, 将其节点值累加, 再除以每层节点数.
注: 需要注意int类型数据溢出的情况, 因而采用long收集总和 或者在收集每个数时先除以每层数量, 但误差需要控制在5-10之内

代码实现:

public List<Double> averageOfLevels(Tre
    Queue<TreeNode> queue = new ArrayDe
    queue.offer(root);
    List<Double> res = new ArrayList<>(
    while (!queue.isEmpty()) {
        int size = queue.size();
        // 总数
        long sum = 0;
        for (int i = 0; i < size; i++) 
            TreeNode poll = queue.poll(
            assert poll != null;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right)
            }
            sum += poll.val;
        }
        // 求平均数
        res.add(sum * 1.0 / size);
    }
    return res;
}

429.N 叉树的层序遍历

429. N 叉树的层序遍历

class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};

public List<List<Integer>> levelOrder(Node root) {
}

思路:
由上可知, 节点下使用列表实现多节点功能, 且子节点数量不确定. 实现方式与102类似, 区别在于用收集列表代替收集左右节点.

代码实现:

public List<List<Integer>> levelOrder(Node root) {
    if (root == null) {
        return new ArrayList<>();
    }
    List<List<Integer>> res = new ArrayList<>();
    Queue<Node> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            Node poll = queue.poll();
            assert poll != null;
            // 收集列表代替收集左右节点
            if (poll.children != null && poll.children.size() > 0) {
                for (int j = 0; j < poll.children.size(); j++) {
                    queue.offer(poll.children.get(j));
                }
            }
            list.add(poll.val);
        }
        res.add(list);
    }
    return res;
}

515.在每个树行中找最大值

515. 在每个树行中找最大值
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

思路:
实现方式与102类似, 区别在于遍历层节点时, 对比较节点, 获取最大值收集之.

代码实现:

public List<Integer> largestValues(TreeNode root)
    if (root == null) {
        return new ArrayList<>();
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    List<Integer> res = new ArrayList<>();
    while (!queue.isEmpty()) {
        int size = queue.size();
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < size; i++) {
            TreeNode poll = queue.poll();
            assert poll != null;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
            // 比较获取最大值
            max = Math.max(max, poll.val);
        }
        res.add(max);
    }
    return res;
}

116.填充每个节点的下一个右侧节点指针

116. 填充每个节点的下一个右侧节点指针
填充每个节点的下一个右侧节点指针

思路:
实现方式与102类似, 只是在遍历的过程中, 当当前节点执行队列中的下一节点,
注: 要避免层级的最后一个节点指向下一层节点

代码实现:

class Node {
    public int val;
    public Node left;
    public Node right;
    public Node next;
    public Node() {
    }
    public Node(int _val) {
        val = _val;
    }
    public Node(int _val, Node _left, Node _right, Node _next) {
        val = _val;
        left = _left;
        right = _right;
        next = _next;
    }
};

public Node connect(Node root) {
    if (root == null) {
        return null;
    }
    Queue<Node> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            Node poll = queue.poll();
            assert poll != null;
            // 连接
            if (i < size - 1) {
                poll.next = queue.peek();
            }
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
        }
    }
    return root;
}

104.二叉树的最大深度

104. 二叉树的最大深度
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。

思路:
实现方式与102类似, 只是在遍历时记录下层数, 遍历结束后返回.

代码实现:

public int maxDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    int level = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode poll = queue.poll();
            assert poll != null;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
        }
        // 记录层数
        level++;
    }
    return level;
}

111. 二叉树的最小深度

111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

思路:
实现方式与102类似, 只不过在遍历时, 去判断节点的左右节点是否为空, 当遇到第一个子节点左右为空的情况即为最小深度.

代码实现:

public int minDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    int level = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        level++;
        for (int i = 0; i < size; i++) {
            TreeNode poll = queue.poll();
            assert poll != null;
            // 左右节点都为空
            if (poll.left == null && poll.right == null) {
                return level;
            }
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
        }
    }
    return level;
}

三. 二叉树练习

226.翻转二叉树

226.翻转二叉树

思路: 遍历
实现方式与102类似, 在遍历时做节点交换

代码实现:

public TreeNode invertTree(TreeNode root) {
    if (root == null) {
        return null;
    }
    Queue<TreeNode> queue = new ArrayDeque<>();
    queue.offer(root);
    while (!queue.isEmpty()) {
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            TreeNode poll = queue.poll();
            assert poll != null;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
            // 交换左右节点
            TreeNode temp = poll.left;
            poll.left = poll.right;
            poll.right = temp;
        }
    }
    return root;
}

思路: 递归 todo

代码实现:

class Solution {
	public TreeNode invertTree(TreeNode root) {
		//递归函数的终止条件,节点为空时返回
		if(root==null) {
			return null;
		}
		//下面三句是将当前节点的左右子树交换
		TreeNode tmp = root.right;
		root.right = root.left;
		root.left = tmp;
		//递归交换当前节点的 左子树
		invertTree(root.left);
		//递归交换当前节点的 右子树
		invertTree(root.right);
		//函数返回时就表示当前这个节点,以及它的左右子树
		//都已经交换完了
		return root;
	}
}

101. 对称二叉树

101. 对称二叉树
给你一个二叉树的根节点 root , 检查它是否轴对称。
对称二叉树
思路: 递归
由根节点兵分两路, 左节点和右节点开始比较, 先左节点的左子项和右节点的右子项比较, 直至最外层遍历完, 再回退到外层倒数第二个节点, 比较其内层. 由此先外而内, 依次递归比较

代码实现:

public boolean isSymmetric(TreeNode root) {
    if (root == null) {
        return true;
    }
    TreeNode left = root.left;
    TreeNode right = root.right;
    return compare(left, right);
}
/**
 * 1.确定递归函数的参数和返回值
 * 2.确定终止条件
 * 3.确定单层递归的逻辑
 */
private boolean compare(TreeNode left, TreeNode right) {
    // 确定终止条件
    if (left == null && right == null) {
        return true;
    } else if (left != null && right == null) {
        return false;
    } else if (left == null) {
        return false;
    } else if (left.val != right.val) {
        return false;
    }
    // 确定单层递归的逻辑
    // 先向外走
    boolean outside = compare(left.left, right.right);
    // 最外层走完 回退向内层走
    boolean inside = compare(left.right, right.left);
    // 内外都走完 判断父节点 返回值若在中止条件返回 则不对称
    return outside && inside;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值