首页 > 其他分享 >005_二叉树

005_二叉树

时间:2024-02-08 22:11:24浏览次数:26  
标签:结点 return cur 二叉树 005 null root left

1. 用递归和非递归的方式实现二叉树的先序、中序、后序遍历

先序遍历

递归

public void preOrderRecur(TreeNode root){
    if (root == null){
        return;
    }
    System.out.println(root.val + " ");
    preOrderRecur(root.left);
    preOrderRecur(root.right);
}

非递归

  • 从栈中弹出一个结点cur
  • 打印,即访问该结点
  • 先把cur的右孩子入栈,再将左孩子入栈
  • 重复上述步骤

中序遍历

递归

public void inOrderRecur(TreeNode root){
    if (root == null){
        return;
    }
    inOrderRecur(root.left);
    System.out.println(root.val + " ");
    inOrderRecur(root.right);
}

递归

  • 将子树的左孩子入栈
  • 直到没有左孩子的时候,弹出结点root
  • 判断root的右孩子是否为空
    • 如果有右孩子结点,重复上面的过程,将这个结点的左孩子入栈
    • 如果没有右孩子,继续弹栈
  • 每次弹出的时候就打印结点,遇到空结点就会弹栈,只要有左孩子就压栈,没有左孩子时就到右孩子上去
public void inOrderUnRecur(TreeNode root){
    if (root == null){
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    while (!stack.isEmpty() || root != null){
        if (root != null){
            stack.push(root);
            root = root.left;
        }else{
            root = stack.pop();
            System.out.println(root.val + " ");
            root = root.right;
        }
    }
}

后序遍历

递归

public void postOrderRecur(TreeNode root){
    if (root == null){
        return;
    }
    postOrderRecur(root.left);
    postOrderRecur(root.right);
    System.out.println(root.val + " ");
}

非递归

先序遍历是中左右,后序遍历是左右中,调整一下顺序,使得遍历时为中右左,然后翻转就得到左右中

  • 从栈中弹出一个结点cur
  • cur放入收集数组中
  • 先将cur的左孩子入栈,再将右孩子入栈
  • 重复上述步骤
  • 翻转收集数组

也可以用一个栈来收集结果,由于栈是先进后出,就不用再翻转了

public void postOrderUnRecur(TreeNode root){
    if (root == null){
        return;
    }
    Stack<TreeNode> stack = new Stack<>();
    List<TreeNode> list = new ArrayList<>();
    stack.push(root);
    while (!stack.isEmpty()){
        TreeNode cur = stack.pop();
        list.add(cur);
        if (cur.left != null){
            stack.push(cur.left);
        }
        if (cur.right != null){
            stack.push(cur.right);
        }
    }
    Collections.reverse(list);
    list.forEach(node -> {
        System.out.println(node.val);
    });
}

2. 二叉树的最大宽度

为二叉树的结点编号

可以得到每一层的结点数量,第一个和最后一个结点的编号差值,就是每一层的宽度

public int getMaxWidth(TreeNode root){
    if (root == null){
        return 0;
    }
    LinkedList<TreeNode> queue = new LinkedList<>();
    HashMap<TreeNode, Integer> map = new HashMap<>();
    int ans = Integer.MIN_VALUE;
    queue.add(root);
    map.put(root, 0);
    while (!queue.isEmpty()){
        int size = queue.size();//每一层的结点数量
        int width = map.get(queue.get(size - 1)) - map.get(queue.get(0)) + 1;
        ans = Math.max(ans, width);
        while (size > 0){
            TreeNode cur = queue.pop();
            if (cur.left != null){
                queue.add(cur.left);
                map.put(cur.left, map.get(cur) * 2 + 1);
            }
            if (cur.right != null){
                queue.add(cur.right);
                map.put(cur.right, map.get(cur) * 2 + 2);
            }
            size--;
        }
    }
    return ans;
}

3. 判断一棵二叉树是否是搜索二叉树

递归

左子树上所有结点的值均小于它的根结点;右子树上所有结点均大于它的根结点。以中序遍历序列表示的时候是从小到大,所有结点的大小就是位于第一个结点和最后一个结点中间的。

isValidBST(TreeNode node, long lower, long upper)表示以node为根的子树,它的结点是否在lower和upper的范围里。

public boolean isBST(TreeNode root){
    return isValidBST(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
}

public boolean isValidBST(TreeNode node, long lower, long upper){
    if (node == null){
        return true;
    }
    //node的 大小越界
    if (node.val <= lower || node.val >= upper){
        return false;
    }
    //递归左子树时修改上界,表示左子树所有结点都小于它的根结点
    //递归右子树同理
    return isValidBST(node.left, lower, node.val) && isValidBST(node.right, node.val, upper);
}

中序遍历

搜索二叉树的中序序列是递增的,所以中序遍历的时候判断当前结点是否大于上一个结点

public int pre = Integer.MIN_VALUE;
public boolean isBST2(TreeNode root){
    if (root == null){
        return true;
    }
    //中序遍历
    boolean left = isBST2(root.left);
    //判断左子树是否是搜索二叉树
    if (!left){
        return false;
    }
    //如果中序遍历时当前子树的根结点小于上一个结点,就说明这不是搜索二叉树
    if (root.val <= pre){
        return false;
    }else {
        //左子树包括根结点构成搜索二叉树,更新pre
        pre = root.val;
    }//左子树是搜索二叉树
    return isBST2(root.right);//左子树是搜索二叉树,当右子树也是搜索二叉树时,以root为根的树就是搜索二叉树
}

中序遍历非递归

public boolean isBST3(TreeNode root){
    if (root == null){
        return true;
    }
    Stack<TreeNode> stack = new Stack<>();
    while (!stack.isEmpty() || root != null){
        if (root != null){
            stack.push(root);
            root = root.left;
        }else{
            root = stack.pop();
            // System.out.println(root.val + " ");
            if (pre >= root.val){
                return false;
            }else {
                pre = root.val;
            }
            root = root.right;
        }
    }
    return true;
}

通用套路

当我们要判断root是否是搜索二叉树的时候,我们需要:

  • 左子树是搜索二叉树
  • 右子树是搜索二叉树
  • root大于左子树的最大值
  • root小于右子树的最小值

所以我们需要的左子树信息是:

  • 左子树是否是搜索二叉树
  • 左子树的最大值

需要的右子树的信息:

  • 右子树是否是搜索二叉树
  • 右子树的最小值

结果左右子树的规模就不一样了,那么我们就规定递归需要三个信息,将递归的规模统一:

  • 子树是否是搜索二叉树
  • 子树的最大值
  • 子树的最小值

有了左右子树的三个信息后,就可以知道root是否是搜索二叉树,以及搜索二叉树的最大值和最小值。

public boolean isBST4(TreeNode root){
    return process(root).isBST;
}
public class ReturnType{
    boolean isBST;
    int max;
    int min;
    public ReturnType(boolean isBST, int max, int min) {
        this.isBST = isBST;
        this.max = max;
        this.min = min;
    }
}
public ReturnType process(TreeNode root){
    if (root == null){
        return null;
    }
    ReturnType leftData = process(root.left);
    ReturnType rightData = process(root.right);

    int min = root.val;
    int max = root.val;
    //求了左子树的最大值和最小值后,又去与右子树比较,可以得到root的最大值和最小值
    //左子树的最大值和最小值
    if (leftData != null){
        min = Math.min(min, leftData.min);
        max = Math.max(max, leftData.max);
    }
    //右子树的最大值和最小值
    if (rightData != null){
        min = Math.min(min, rightData.min);
        max = Math.max(max, rightData.max);
    }//min是root树下的最小值;max是root树下的最大值。然后最后一行代码返回的时候,就得到了当前树也就是root的最大值和最小值
    
    boolean isBST = true;
    //左子树不平衡;或左子树的根小于等于左子树中的最小值,那么root这棵树就不平衡
    if (leftData != null && (!leftData.isBST || leftData.max >= root.val)){
        isBST = false;
    }
    //右子树同理
    if (rightData != null && (!rightData.isBST || rightData.min <= root.val)){
        isBST = false;
    }

    //返回我们需要的三个信息
    return new ReturnType(isBST,max,min);
}

4. 判断完全二叉树

  • 采用层序遍历
  • 如果一棵子树的根结点,它的左子树为空但右子树不为空,则这颗子树一定不是完全二叉树
  • 满足上面条件的情况下,如果遇到了一棵子树,左孩子不为空但右孩子为空,则之后层序遍历的所有结点必须是叶子结点
public boolean isCBT(TreeNode root){
    if (root == null){
        return true;
    }
    LinkedList<TreeNode> queue = new LinkedList<>();
    boolean leaf = false;
    queue.add(root);
    while (!queue.isEmpty()){
        TreeNode cur = queue.poll();
        if ((leaf && (cur.left!=null || cur.right != null)) || (cur.left == null && cur.right != null)){
            return false;
        }
        if (cur.left != null && cur.right == null){
            leaf = true;
        }
        if (cur.left != null){
            queue.add(cur.left);
        }
        if (cur.right != null){
            queue.add(cur.right);
        }
    }
    return true;
}

5. 判断平衡二叉树

后序遍历

对二叉树做后序遍历,从底至顶返回子树深度,若遇到子树不是平衡二叉树,则进行剪枝,直接向上返回。

  • 返回值
    • 当左右子树深度差小于等于1,就返回当前子树的深度
    • 当左右子树深度差大于1,返回-1,表示子树不是平衡二叉树
  • 终止条件
    • 当root为空,返回高度为0
    • 当左子树或者右子树深度为-1,说明左子树或者右子树不是平衡二叉树,剪枝,返回-1
public boolean isBalanced(TreeNode root){
    return recur(root) != -1;
}

public int recur(TreeNode root){
    if (root == null){
        return 0;
    }
    int left = recur(root.left);
    //剪枝
    if (left == -1){
        return -1;
    }
    int right = recur(root.right);
    //剪枝
    if (right == -1){
        return -1;
    }
    return Math.abs(left - right) <= 1 ? Math.max(left, right) + 1 : -1;
}

通用套路

一棵树是平衡二叉树必须满足三个条件:左子树是平衡二叉树;右子树是平衡二叉树;左右子树高度差小于等于1。

要判断root是否是平衡二叉树,我们就需要根据左右子树的信息来进行判断。

需要的左子树的信息:

  • 左子树是否是平衡二叉树
  • 左子树的高度

需要的右子树的信息:

  • 右子树是否是平衡二叉树
  • 右子树的高度

有了左右子树的信息,就可以知道左右子树是否是平衡二叉树,以及左右子树的高度差,然后就可以判断root是否是平衡二叉树。

在进行递归的时候,我们就需要:子树的高度、子树是否是平衡二叉树。返回的时候就可以得到root的两个信息。

public boolean isBalanced2(TreeNode root){
    return process(root).isBalanced;
}
public class ReturnType{
    public boolean isBalanced;//是否是平衡二叉树
    public int height;//高度

    public ReturnType(boolean isBalanced, int height) {
        this.isBalanced = isBalanced;
        this.height = height;
    }
}

public ReturnType process(TreeNode root){
    if (root == null){
        return new ReturnType(true, 0);//高度为0的平衡二叉树
    }

    ReturnType leftData = process(root.left);
    ReturnType rightData = process(root.right);

    int height = Math.max(leftData.height, rightData.height) + 1;
    //左子树是平衡二叉树、右子树是平衡二叉树、左右子树高度差小于等于1
    boolean isBalanced = leftData.isBalanced && rightData.isBalanced && Math.abs(leftData.height - rightData.height) <= 1;

    return new ReturnType(isBalanced, height);
}

6. 判断满二叉树

public boolean isFullTree(TreeNode root){
    if (root == null){
        return true;
    }
    ReturnType ret = isFull(root);
    return ret.node_cnt == (1 << ret.node_cnt - 1);
}

public class ReturnType{
    int height;
    int node_cnt;
    public ReturnType(int height, int node_cnt) {
        this.height = height;
        this.node_cnt = node_cnt;
    }
}
public ReturnType isFull(TreeNode root){
    if (root == null){
        return new ReturnType(0, 0);
    }

    ReturnType leftData = isFull(root.left);
    ReturnType rightData = isFull(root.right);

    int height = Math.max(leftData.height, rightData.height) + 1;
    int node_cnt = leftData.node_cnt + rightData.node_cnt + 1;

    return new ReturnType(height, node_cnt);
}

7. 给定两个二叉树的结点p和q,找到它们的最低公共祖先结点

递归

终止条件

  • root为空时返回null

  • root等于p或者q时,返回root

    如果p是q的根或者q是p的根,那么直接返回p或者q

    如果pq在两侧,加入先递归到p,则返回p,后面遇到q的时候返回q,就会满足返回值中的第二种情况。

递推

  • 递归左子树,返回left
  • 递归右子树,返回right

返回值

  • 当left和right都为空,返回null

    说明root的左右子树中都不包含p和q

  • 当left和right都不为空,返回root

    说明p、q分列在root的异侧,即分别在root的左右子树,则root为p和q的最近公共祖先

  • 当left为空,right不为空,返回right

    说明pq都不在root的左子树中

  • 当left不为空,right为空,返回left

TreeNode lowestAncestor(TreeNode root, TreeNode p, TreeNode q){
    if (root == null || root == p || root == q){
        return root;
    }

    TreeNode left = lowestAncestor(root.left, p, q);
    TreeNode right = lowestAncestor(root.right, p, q);

    // if (left == null && right == null){
    //     return null;
    // }
    // if (left != null && right != null){
    //     return root;
    // }
    // if (left == null && right != null){
    //     return right;
    // }
    // if (left != null && right == null){
    //     return left;
    // }
    if (left == null){
        return right;
    }
    if (right == null){
        return left;
    }
    return root;
}

哈希表

哈希表fatherMap存储:key是当前结点,value是key的父结点

由于fatherMap表,我们就可以从p结点开始,依次获得它的父结点,然后一直往上直到根结点,将这个路线中的所有父结点存储在set中。

再从q开始,往上走,如果过程中遇到的结点出现在了set中,说明这个结点就是pq的公共祖先。

public TreeNode lowestAncestor2(TreeNode root, TreeNode p, TreeNode q){
    HashMap<TreeNode, TreeNode> fatherMap = new HashMap<>();
    //先将根结点放入map
    fatherMap.put(root, root);
    //递归,将所有结点和其父结点的键值对放入表中
    recur(root, fatherMap);
    HashSet<TreeNode> set = new HashSet<>();
    TreeNode cur = p;
    while (fatherMap.get(cur) != cur){//根结点的父结点等于本身
        set.add(cur);
        cur = fatherMap.get(cur);//遍历父结点
    }
    //根结点也要加入set中
    set.add(cur);
    cur = q;
    while (!set.contains(cur)){
        cur = fatherMap.get(cur);
    }
    return cur;
}

public void recur(TreeNode root, HashMap<TreeNode, TreeNode> fatherMap){
    if (root == null){
        return;
    }
    fatherMap.put(root.left, root);
    fatherMap.put(root.right, root);
    recur(root.left, fatherMap);
    recur(root.right, fatherMap);
}

8. 在二叉树中找到一个结点的后继结点

image-20240207222018256

  • x的前驱结点是x的左子树的最右结点,后继节点是右子树的最左结点

  • 当x有右子树的时候,右子树的最左结点就是x的后继结点

  • 当x无右子树的时候,假设x的后继结点是y(y的前驱节点是x),则y的左子树的最右结点是x。如何找到y?

    • 所以我们需要从x出发,向上寻找,依次遍历x的父结点。直到x是某个结点的左孩子,则这个结点就是我们要找的y,这个结点的左子树的最右结点就是x。

    • 如果parent为空的时候都没有找到,就说明x是整棵树的最右结点,即中序遍历下的最后一个结点,它没有后继结点。

    image-20240208220531893

public class Node {
    public int value;
    public Node left;
    public Node right;
    public Node parent;

    public Node(int data) {
        this.value = data;
    }
}

public Node getSuccessorNode(Node node){
    if (node == null){
        return null;
    }
    if (node.right != null){
        return getLeftMost(node.right);
    }
    Node parent = node.parent;
    //node != parent.left :从x出发,向上寻找一个父结点,这个父结点是y的左子树的时候,就找到了y是x的后继结点
    while (parent != null && node != parent.left){
        node = parent;
        parent = node.parent;
    }
    return parent;
}

public Node getLeftMost(Node node){
    if (node == null){
        return null;
    }
    while (node.left != null){
        node = node.left;
    }
    return node;
}

9. 二叉树的序列化和反序列化

先序遍历

/**
     * 先序序列化
     * @param root
     * @return
     */
public String serialByPre(TreeNode root){
    if (root == null){
        return "#";
    }
    String res = root.val + ",";
    res += serialByPre(root.left);
    res += serialByPre(root.right);
    return res;
}

/**
     * 先序反序列化
     * @param preStr
     * @return
     */
public TreeNode reconByPreString(String preStr){
    String[] values = preStr.split(",");
    return reconPreOrder(values);
}

int index = 0;
public TreeNode reconPreOrder(String[] values){
    String value = values[index++];

    if (value.equals("#")){
        return null;
    }

    TreeNode root = new TreeNode(Integer.valueOf(value));
    root.left = reconPreOrder(values);
    root.right = reconPreOrder(values);
    return root;
}

层序遍历

public String serialByLevel(TreeNode root){
    if (root == null){
        return "#,";
    }
    String res = root.val + ",";
    LinkedList<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()){
        TreeNode cur = queue.poll();
        TreeNode left = cur.left;
        TreeNode right = cur.right;
        if (left != null){
            queue.add(left);
            res += left.val + ",";
        }else {
            res += "#,";
        }
        if (right != null){
            queue.add(right);
            res += right.val + ",";
        }else {
            res += "#,";
        }
    }
    return res;
}

public TreeNode reconByLevelString(String levelStr){
    String[] values = levelStr.split(",");
    int index = 0;
    LinkedList<TreeNode> queue = new LinkedList<>();
    if (values[index].equals("#")){
        return null;
    }
    TreeNode root = new TreeNode(Integer.valueOf(values[index++]));
    queue.add(root);
    while (!queue.isEmpty()){
        TreeNode cur = queue.poll();

        String leftValue = values[index++];
        if (leftValue.equals("#")){
            cur.left = null;
        }else {
            cur.left = new TreeNode(Integer.valueOf(leftValue));
            queue.add(cur.left);
        }
        String rightValue = values[index++];
        if (rightValue.equals("#")){
            cur.right = null;
        }else {
            cur.right = new TreeNode(Integer.valueOf(rightValue));
            queue.add(cur.right); 
        }
    }
    return root;
}

10. 折纸问题

image-20240208134930623

  • 折纸的次数对应二叉树的层数
  • 凹折痕位于左子树,凸折痕位于右子树
  • 二叉树中序遍历的结果就是折痕的顺序
//i结点的层数
//down==true表示凹结点;down==false表示凸结点
public void printProcess(int i, int N, boolean down){
    //层数越界
    if (i > N){
        return;
    }
    //模拟二叉树
    printProcess(i + 1, N, true);//模拟遍历左子树,表示递归到第i+1层,是第i层的左子树
    System.out.println(down ? "凹" : "凸");
    printProcess(i + 1, N, false);
}

printProcess(1, N, true);

标签:结点,return,cur,二叉树,005,null,root,left
From: https://www.cnblogs.com/jyyyy/p/18012182

相关文章

  • (15/60)层序遍历、翻转二叉树、对称二叉树
    层序遍历leetcode:102.二叉树的层序遍历107.二叉树的层序遍历II199.二叉树的右视图637.二叉树的层平均值429.N叉树的层序遍历515.在每个树行中找最大值116.填充每个节点的下一个右侧节点指针104.二叉树的最大深度111.二叉树的最小深度102.二叉树的层序遍......
  • 代码随想录算法训练营第十五天| 层序遍历 10 226.翻转二叉树 101.对称二叉树 2
    层序遍历  102.二叉树的层序遍历-力扣(LeetCode)思路:结合了昨天学到的标记法,每当一层遍历完,向队列中添加一个NULL标记。遍历到NULL节点表示该层遍历完了,将结果存入结果集中。/***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNo......
  • P2585 [ZJOI2006] 三色二叉树
    原题链接总结1.要学会动态规划这种思维方式,即定义状态和状态之间的转移2.本题的难点在于如何将抽象的输入数据转换成树状结构处理和定义状态,这个定义状态让我想到了初中添加几何线,可能要多做题才能有感觉吧3.有一定模拟的部分,这一部分要细心\(Code\)#include<bits/stdc++.h>......
  • (14/60)二叉树理论基础、递归遍历、迭代遍历、统一迭代
    二叉树理论基础分类满二叉树:只有度为0和度为2的结点,且度为0结点在同一层上(每一层的结点都是满的)。完全二叉树:除了底层外其他层结点都是满的(底层当然也可以是满的),且底层结点从左往右连续不断。二叉搜索树:树的每个结点满足:左子树所有结点值均小于根结点的值右子树所有......
  • 代码随想录算法训练营第十四天 | 94. 二叉树的中序遍历,144.二叉树的前序遍历,145.二叉
    145.二叉树的后序遍历 已解答简单 相关标签相关企业 给你一棵二叉树的根节点 root ,返回其节点值的 后序遍历 。 示例1:输入:root=[1,null,2,3]输出:[3,2,1]示例2:输入:root=[]输出:[]示例3:输入:root=[1]输出:[1] 提示:树中节点......
  • 一套模板搞定二叉树算法题--二叉树算法讲解004
    1、二叉树经典习题模拟忘记知识点和技巧时,遇到一个新的二叉树习题,该如何处理思考和写代码解题?1.1、leetcode965题目和题意:题解1成员变量self.ans:题解2递归回传:1.2、leetcode257该题是个经典二叉树题目题目和题意:题解:分析,所有路径,每一个叶子节点都需要到达。到......
  • 执行truncate时报错:ORA-00054:资源正忙但指定以NOWAIT 方式获取资源或者超时失效,怎样
    在执行TRUNCATE语句时出现错误,可能是由于以下原因之一:表正在被其他会话使用:如果表正在被其他会话使用,您将无法执行TRUNCATE操作。请确保没有其他会话正在使用该表,并尝试再次执行TRUNCATE。权限不足:如果您没有足够的权限来执行TRUNCATE操作,则会收到错误消息。请确保您具有足......
  • leedcode 对称二叉树
    迭代法:#Definitionforabinarytreenode.#classTreeNode:#def__init__(self,val=0,left=None,right=None):#self.val=val#self.left=left#self.right=rightclassSolution:defisSymmetric(self,root):......
  • leedcode 二叉树的中序遍历
    自己写的classSolution:def__init__(self):self.res_list=list()definorderTraversal(self,root):ifroot:ifroot==None:returnelse:self.inorderTraversal(root.left)......
  • Qt Access violation - code c0000005 debug write access violation
    WhentryingtodebugmyQtapplication,theappthrowaexceptionas"Exceptionat0x77da2073,code:0xc0000005:writeaccessviolationat:0x1,flags=0x0"IamusingQtcreatorandvs2005compileranddebugger. Iloadtheprojectonvs2005a......