数据结构 -- 二叉树二叉搜索树

二叉树

二叉树是这么一种树状结构:每个节点最多有两个孩子,左孩子和右孩子

重要的二叉树结构

  • 完全二叉树(complete binary tree)是一种二叉树结构,除最后一层以外,每一层都必须填满,填充时要遵从先左后右

  • 平衡二叉树(balance binary tree)是一种二叉树结构,其中每个节点的左右子树高度相差不超过 1

1) 存储

存储方式分为两种

  1. 定义树节点与左、右孩子引用(TreeNode)

  2. 使用数组,前面讲堆时用过,若以 0 作为树的根,索引可以通过如下方式计算

    • 父 = floor((子 - 1) / 2)

    • 左孩子 = 父 * 2 + 1

    • 右孩子 = 父 * 2 + 2

 没有孩子的节点也有一个称呼:叶子结点

我们之前学优先级队列和堆结构的时候其实都接触过,比如我们之前学的大顶堆

当然大顶堆这种二叉树属于比较特殊的二叉树,叫完全二叉树,也就是除了最后一层以外,每一层都得填满而且填充的顺序必须从左到右填充

遍历

遍历也分为两种

1.广度优先遍历(Breadth-first-order):尽可能先访问离根最近的节点,也称为层序遍历

2.深度优先遍历(Depth-first-order):对于二叉树,可以进一步分为三种

        1.pre-order前序遍历,对于一棵子树,先访问该节点,然后是左子树,最后是右子树

        2.in-order中序遍历,对于每一颗子树,先访问左子树,然后是该节点,最后是右子树

        3.post-order后序遍历,对于每一棵子树,先访问左子树,然后是右子树,最后是该节点

本轮开始时队列本轮访问节点
[1]1
[2, 3]2
[3, 4]3
[4, 5, 6]4
[5, 6]5
[6, 7, 8]6
[7, 8]7
[8]8
[]
  1. 初始化,将根节点加入队列

  2. 循环处理队列中每个节点,直至队列为空

  3. 每次循环内处理节点后,将它的孩子节点(即下一层的节点)加入队列

注意

  • 以上用队列来层序遍历是针对 TreeNode 这种方式表示的二叉树

  • 对于数组表现的二叉树,则直接遍历数组即可,自然为层序遍历的顺序

深度优先

 

 递归

public class TreeTraversal {
    public static void main(String[] args) {
        /*
                 1
                / \
               2   3
              /    /\
             4    5  6
         */
        TreeNode root = new TreeNode(
                new TreeNode(new TreeNode(4),2,null),
                1,
                new TreeNode(new TreeNode(5),3,new TreeNode(6))
        );
        preOrder(root);//1	2	4	3	5	6
        System.out.println();

        inOrder(root);//4	2	1	5	3	6
        System.out.println();

        PostOrder(root);
        System.out.println();
    }
    /**
     * 前序遍历
     * @Params:node-节点
     */
    static void preOrder(TreeNode node){
        if(node==null){
            return;
        }
        System.out.print(node.val+"\t");//当前节点值
        preOrder(node.left);// 左
        preOrder(node.right);// 右

    }

    /**
     * 中序遍历
     * @Params:node-节点
     */
    static void inOrder(TreeNode node){
        if(node==null){
            return;
        }
        inOrder(node.left); //左
        System.out.print(node.val+"\t");//值
        inOrder(node.right);//右
    }

    /**
     * 后序遍历
     * @Params:node-节点
     */
    static void PostOrder(TreeNode node){
        if(node==null){
            return;
        }
        PostOrder(node.left);
        PostOrder(node.right);
        System.out.print(node.val+"\t");

    }
}

非递归

非递归实现

前序遍历 这里的LinkedListStack是自己实现的 也可以用Java自带的LInkedList

LinkedListStack<TreeNode> stack = new LinkedListStack<>();
TreeNode curr = root;
​
while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        System.out.println(curr);
        curr = curr.left;
    } else {
        TreeNode pop = stack.pop();
        curr = pop.right;
    }
​
}

中序遍历

LinkedListStack<TreeNode> stack = new LinkedListStack<>();
TreeNode curr = root;
​
while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        curr = curr.left;
    } else {
        TreeNode pop = stack.pop();
        System.out.println(pop);
        curr = pop.right;
    }
}

后序遍历

LinkedListStack<TreeNode> stack = new LinkedListStack<>();
TreeNode curr = root;
TreeNode pop = null;
​
while (!stack.isEmpty() || curr != null) {
    if (curr != null) {
        stack.push(curr);
        curr = curr.left;
    } else {
        TreeNode peek = stack.peek();
        if (peek.right == null || peek.right == pop) {
            pop = stack.pop();
            System.out.println(pop);
        } else {
            curr = peek.right;
        }
    }
}

对于后序遍历,向回走时,需要处理完右子树才能 pop 出栈。如何知道右子树处理完成呢?

  • 如果栈顶元素的 right==null 表示没啥可处理的,可以出栈

  • 如果栈顶元素的right!=null,

    • 那么使用 lastPop 记录最近出栈的节点,即表示从这个节点向回走

    • 如果栈顶元素的 right==lastPop 此时应当出栈

对于前、中两种遍历,实际以上代码从右子树向回走时,并未走完全程(stack 提前出栈了)后序遍历以上代码是走完全程了

统一写法

下面是一种统一的写法,依据后序遍历修改

LinkedList<TreeNode> stack = new LinkedList<>();
​
TreeNode curr = root; // 代表当前节点
TreeNode pop = null; // 最近一次弹栈的元素
while (curr != null || !stack.isEmpty()) {
    if (curr != null) {
        colorPrintln("前: " + curr.val, 31);
        stack.push(curr); // 压入栈,为了记住回来的路
        curr = curr.left;
    } else {
        TreeNode peek = stack.peek();
        // 右子树可以不处理, 对中序来说, 要在右子树处理之前打印
        if (peek.right == null) {
            colorPrintln("中: " + peek.val, 36);
            pop = stack.pop();
            colorPrintln("后: " + pop.val, 34);
        }
        // 右子树处理完成, 对中序来说, 无需打印
        else if (peek.right == pop) {
            pop = stack.pop();
            colorPrintln("后: " + pop.val, 34);
        }
        // 右子树待处理, 对中序来说, 要在右子树处理之前打印
        else {
            colorPrintln("中: " + peek.val, 36);
            curr = peek.right;
        }
    }
}
​
public static void colorPrintln(String origin, int color) {
    System.out.printf("\033[%dm%s\033[0m%n", color, origin);
}

将回的代码注释掉那就是一个前序遍历代码

将去的代码注释掉那就是一个中序遍历代码

练习

101. 对称二叉树 - 力扣(LeetCode)

代码实现:

public class E04Leetcode101 {
    public boolean isSymmetric(TreeNode root){
        return check(root.left,root.right);
    }

    private boolean check(TreeNode left, TreeNode right) {
        if(left==null&&right==null){
            return true;
        }
        //执行到这里说明:left 和 right不能同时为null
        if(right==null||left==null){
            return false;
        }
        if(left.val!=right.val){
            return false;
        }
        return check(left.left,right.right) && check(left.right,right.left);



    }
}

测试类:

import org.junit.Test;

import static org.junit.Assert.assertTrue;

public class TestE04Leetcode101 {
    @Test
    public void test1(){
        TreeNode root = new TreeNode(
                new TreeNode(new TreeNode(3),2,new TreeNode(4)),
                1,
                new TreeNode(new TreeNode(4),2,new TreeNode(3))
        );
        assertTrue(new E04Leetcode101().isSymmetric(root));
    }

    @Test
    public void test2(){
        TreeNode root = new TreeNode(
                new TreeNode(null,2,new TreeNode(3)),
                1,
                new TreeNode(null,2,new TreeNode(3))
        );
        assertTrue(new E04Leetcode101().isSymmetric(root));
    }
}

104. 二叉树的最大深度 - 力扣(LeetCode)

分析:

/**
 * 二叉树最大深度-使用后序遍历求解
 */
public class Leetcode104_1 {
    /*
        思路:
        1.得到左子树深度,得到右子树深度,二者最大值+1就是本节点深度
        2.因为需要先得到左右子树深度,很显然是后序遍历典型应用
        3.关于深度的定义:从根出发,离根最远的节点总边数
            注意:力扣里的深度定义要多一

            深度2         深度3          深度1
            1              1               1
           / \            / \
          2   3          2   3
                              \
                               4
     */

    public int maxDepth(TreeNode node){
         if(node==null){
             return 0;
         }
         if(node.left==null&&node.right==null){
             return 1;
         }
         int d1 = maxDepth(node.left);
         int d2 = maxDepth(node.right);
         return Integer.max(d1,d2)+1;
    }
}

注:上面代码中其实可以把第二个if去掉

第二种解法:

import java.util.LinkedList;

/**
 * 二叉树最大深度 - 使用后序遍历(非递归)求解
 */
public class E05Leetcode104_2 {
    /*
        思路:
        1.使用非递归后序遍历,栈的最大高度即为最大深度
     */
    public int maxDepth(TreeNode root){
        TreeNode curr = root;
        TreeNode pop = null;
        LinkedList<TreeNode>stack = new LinkedList<>();
        int max = 0;//栈的最大高度
        while(curr!=null || !stack.isEmpty()){
            if(curr!=null){
                stack.push(curr);
                int size = stack.size();
                if(size>max){
                    max = size;
                }
                curr=curr.left;
                
            }else{
                TreeNode peek = stack.peek();
                if(peek.right==null||peek.right==pop){
                    pop=stack.pop();
                }else{
                    curr=peek.right;
                }
            }
        }
        return max;
    }
}

第三张方法:

 代码实现:

public class E05Leetcode104_3 {
    /*
        思路:
        1.使用层序遍历,层数即最大深度
     */
    public int maxDepth(TreeNode root){
        Queue<TreeNode> queue= new LinkedList<>();
        //LinkedList:可以作为双向链表,队列,栈  "身兼数职"
        queue.offer(root);
        int depth=0;
//        int c1=1;//记录每层有几个元素
        while(!queue.isEmpty()){
            //queue.size()
//            int c2=0;
            int size = queue.size();
            for(int i=0;i<size;i++){
                TreeNode poll = queue.poll();
//                System.out.print(poll.val+"\t");
                if(poll.left!=null){
                    queue.offer(poll.left);
//                    c2++;
                }
                if(poll.right!=null){
                    queue.offer(poll.right);
//                    c2++;
                }
            }
//            c1=c2;
//            System.out.println();
            depth++;

        }
        return depth;

    }

}
if(root==null){
    return 0;
}
//有可能传过来的是null

111. 二叉树的最小深度 - 力扣(LeetCode)

class Solution {
    public int minDepth(TreeNode root) {
        if(root==null)return 0;
        int m1 = minDepth(root.left);
        int m2 = minDepth(root.right);
        return root.left==null||root.right==null?m1+m2+1:Math.min(m1,m2)+1;
    }
}
class Solution {
    public int minDepth(TreeNode root){
        if(root == null){
            return 0;
        }
        Queue<TreeNode>queue = new LinkedList<>();
        queue.offer(root);
        int depth = 0;
        while(!queue.isEmpty()){
            int size = queue.size();
            depth++;
            for(int i=0;i<size;i++){

                TreeNode poll = queue.poll();
                if(poll.right==null&&poll.left==null){
                    return depth;
                }
                if(poll.left!=null){
                    queue.offer(poll.left);
                }
                if(poll.right!=null){
                    queue.offer(poll.right);
                }
            }

        }
        return depth;
    }
}

226. 翻转二叉树 - 力扣(LeetCode)

class Solution {
    public TreeNode invertTree(TreeNode root) {
        fn(root);
        return root;
    }
    private void fn(TreeNode node){
        if(node==null){
            return;
        }
        TreeNode t = node.left;
        node.left = node.right;
        node.right = t;
        fn(node.left);
        fn(node.right);

    }
}

import java.util.LinkedList;

/**
 * 根据后缀表达式构造表达式树
 */
public class ExpressionTree {

    public static class TreeNode {
        public String val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(String val) {
            this.val = val;
        }

        public TreeNode(TreeNode left, String val, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }

        @Override
        public String toString() {
            return this.val;
        }
    }

    /*
        中缀表达式  (2-1)*3
        后缀(逆波兰)表达式  21-3*

        1.遇到数字入栈
        2.遇到运算符出栈,建立节点关系,再入栈

        栈
        /   /
        /   /
        /   /
        ----
        '-'.right = 1
        '-'.left = 2

        '*' .right = 3
        '*' .left = '-'

        表达式树
        *
       / \
      -   3
     / \
    2   1
     */
    public TreeNode constructExpressionTree(String[] tokens) {
        LinkedList<TreeNode> stack = new LinkedList<>();
        for (String t : tokens) {
            switch (t) {
                case "+", "-", "*", "/" -> {//运算符
                    TreeNode right = stack.pop();
                    TreeNode left = stack.pop();
                    TreeNode parent = new TreeNode(t);
                    parent.left = left;
                    parent.right = right;
                    stack.push(parent);
                }
                default -> {//数字
                    stack.push(new TreeNode(t));
                }
            }
        }
        return stack.peek();
        //做个后序遍历 检测
    }
}
import Tree.ExpressionTree;
import org.junit.Test;
import java.util.ArrayList;
import static org.junit.Assert.assertArrayEquals;

public class TestExpressionTree {
    ExpressionTree exp = new ExpressionTree();

    @Test
    public void test1(){
        String[] tokens = {"2","1","-","3","*"};
        ExpressionTree.TreeNode root = exp.constructExpressionTree(tokens);
        ArrayList<String>result = new ArrayList<>();
        post(root,result);
        assertArrayEquals(tokens,result.toArray(new String[0]));
    }

    private void post(ExpressionTree.TreeNode node,ArrayList<String>result){
        if(node==null){
            return;
        }
        post(node.left,result);
        post(node.right,result);
        result.add(node.val);
    }
}

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

import java.util.Arrays;

/**
 * 根据前中序遍历结果构造二叉树
 */
public class Leetocde105 {
    /*
        preOrder = {1,2,4,3,6,7};
        inOrder = {4,2,1,6,3,7};

        根1
            pre     in
        左 2 4     4,2
        右 3,6,7   6,3,7

        根 2
        左 4

        根 3
        左 6
        右 7

     */

    public TreeNode buildTree(int[] preOrder,int[] inOrder){
        if(preOrder.length==0||inOrder.length==0){
            return null;
        }
        //创建根节点
        int rootValue = preOrder[0];
        TreeNode root = new TreeNode(rootValue);
        //区分左右子树
        for (int i = 0; i < inOrder.length; i++) {
            if(inOrder[i]==rootValue){
                //0 ~ i-1  左子树
                //i+1 ~ inOrder.length-1 右子树
                int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);//i是不包含的 [4,2]
                int[] inRight= Arrays.copyOfRange(inOrder, i, inOrder.length);//[6,3,7]

                int[] preLeft = Arrays.copyOfRange(preOrder, 1, i + 1);//[2,4]
                int[] preRight = Arrays.copyOfRange(preOrder, i+1, inOrder.length);//[3,6,7]

                root.left = buildTree(preLeft,inLeft); //2
                root.right = buildTree(preRight,inRight);//3
                break;

            }
            
        }
        return root;
    }
}

参考力扣题解:

可以将中序遍历的值和索引存在一个哈希表中,这样就可以一下子找到根结点在中序遍历数组中的索引。

Java
import java.util.HashMap;
import java.util.Map;

class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

public class Solution {

    private int[] preorder;
    private Map<Integer, Integer> hash;

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int preLen = preorder.length;
        int inLen = inorder.length;
        if (preLen != inLen) {
            throw new RuntimeException("Incorrect input data.");
        }
        this.preorder = preorder;
        this.hash = new HashMap<>();
        for (int i = 0; i < inLen; i++) {
            hash.put(inorder[i], i);
        }

        return buildTree(0, preLen - 1, 0, inLen - 1);
    }


    private TreeNode buildTree(int preLeft, int preRight, int inLeft, int inRight) {
        // 因为是递归调用的方法,按照国际惯例,先写递归终止条件
        if (preLeft > preRight || inLeft > inRight) {
            return null;
        }
        // 先序遍历的起点元素很重要
        int pivot = preorder[preLeft];
        TreeNode root = new TreeNode(pivot);
        int pivotIndex = hash.get(pivot);
        root.left = buildTree(preLeft + 1, pivotIndex - inLeft + preLeft,
                inLeft, pivotIndex - 1);
        root.right = buildTree(pivotIndex - inLeft + preLeft + 1, preRight,
                pivotIndex + 1, inRight);
        return root;
    }
}
复杂度分析:

时间复杂度:O(N)O(N)O(N),这里 NNN 是二叉树的结点个数,每调用一次递归方法创建一个结点,一共创建 NNN 个结点,这里不计算递归方法占用的时间。
空间复杂度:O(N)O(N)O(N),这里忽略递归方法占用的空间,因为是对数级别的,比 NNN 小。

作者:liweiwei1419
链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/solutions/8946/qian-xu-bian-li-python-dai-ma-java-dai-ma-by-liwei/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder.length==0||postorder.length==0){
            return null;
        }
        int rootValue = postorder[postorder.length-1];
        TreeNode root = new TreeNode(rootValue);
        for(int i=0;i<inorder.length;i++){
            if(inorder[i] == rootValue){
                int[] inLeft = Arrays.copyOfRange(inorder,0,i);
                int[] inRight =Arrays.copyOfRange(inorder,i+1,inorder.length);

                int[] postLeft = Arrays.copyOfRange(postorder,0,i);
                int[] postRight = Arrays.copyOfRange(postorder,i,postorder.length-1);

                root.left = buildTree(inLeft,postLeft);
                root.right = buildTree(inRight,postRight);
            }
        }
        return root;
    }
}

在我们之前所学的数据结构中我们要查找一个元素,时间复杂度都是O(n) 如果我们想提高查找效率就要引入新的数据结构和算法了

之前学过二分查找

O(logN) 但是这个算法它必须是排好序的 但排序消耗的时间复杂度也是比较高的可能会得不偿失

那么我引入这个新的数据结构叫:

二叉搜索树

平均复杂度是对数时间,但是最糟糕情况是O(n)

get方法的实现

/**
 * Binary Search Tree 二叉搜索树
 */
public class BSTTree1 {
    BSTNode root;//根节点
   static class BSTNode {
        int key;
        Object value;
        BSTNode left;
        BSTNode right;

        public BSTNode(int key){
            this.key = key;

        }

       public BSTNode(int key, Object value) {
           this.key = key;
           this.value = value;
       }

       public BSTNode(int key, Object value, BSTNode left, BSTNode right){
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
   }

    /**
     * <h3>查找关键字对应的值</h3>
     * @param:key-关键字
     * @return:关键字对应的值
     */
   public Object get(int key){

       return doGet(root,key);
   }
  //递归版本

  //尾递归 最后一步调用自身的时候(不含任何其他数) 转换成非递归实现性能更好 因为java不会对尾递归进行优化

   private Object doGet(BSTNode node,int key){
       if(node == null){
           return null;//没有找到
       }
       if(key<node.key){
           return doGet(node.left,key);//向左找
       }else if(node.key < key){
           return doGet(node.right,key);//向右找
       }else{
           return node.value;//找到了
       }
   }


    //非递归版本
   public Object get(int key){
        BSTNode node =root;
        while(node!=null){
            if(key<node.key){
                node = node.left;
            }else if(key>node.key){
                node = node.right;
            }else{
                return node.value;
            }
        }
        return null;
   }

也可以用泛型实现更一般的方法:

/*
//泛型有没有比较大小的能力呢? 泛型中也有一个语法叫做泛型上限
将来我的泛型必须是Comparable的子类型
 */
public class BSTNode2<T extends Comparable<T>,V> {
    static class BSTNode<T,V> {
        T key;
        V value;
        BSTNode<T,V> left;
        BSTNode<T,V> right;

        public BSTNode(T key){
            this.key = key;
        }

        public BSTNode(T key, V value) {
            this.key = key;
            this.value = value;
        }

        public BSTNode(T key, V value, BSTNode<T,V> left, BSTNode<T,V> right){
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    BSTNode<T,V> root;


    /**
     * <h3>查找关键字对应的值</h3>
     * @param:key-关键字
     * @return:关键字对应的值
     */
    public V get(T key){
        BSTNode<T,V> p =root;
        while(p!=null){
            //key p.key
            /*
            -1 : key<p.key
            0  : key=p.key
            1  : key>p.key
             */
            int result = key.compareTo(p.key);
            if(result<0){
                p = p.left;
            }else if (result>0){
                p = p.right;
            }else{
                return p.value;
            }
        }
        return null;
    }

min-max的实现

    /**
     * <h3>查找最小关键字对应值</h3>
     * @Returns:关键字对应的值
     */
//    public Object min(){
//        return  doMin(root);
//    }

    public Object min(){
        if(root==null){
            return null;
        }
        BSTNode P = root;
        while(P.left!=null){
            P = P.left;
        }
        return P.value;
    }

    public Object doMin(BSTNode node){
        if(node==null){
            return null;
        }
        if(node.left == null){//找到了最小的节点
            return node.value;
        }
        return doMin(node.left);//尾递归
    }

    /**
     * <h3>查找最大关键字对应值</h3>
     * @return:关键字对应值
     */
    public Object max(){
        if(root==null){
            return null;
        }
        BSTNode p = root;
        while(p.right!=null){
            p = p.right;
        }
        return p.value;
    }

put的实现 --和java map集合非常相似

/**
     * <h3>存储关键字和对应值</h3>
     * @param:key 关键字
     * @param:value 值
     */
    public void put(int key,Object value){
        //1.key 有  更新
        //2.key 没有 更新
        BSTNode node = root;
        BSTNode parent =null;
        while(node!=null){
            parent = node;
            if(key <node.key){
                node = node.left;
            }else if(node.key<key){
                node=node.right;
            }else{
                //1
                node.value = value;
                return;
            }
        }
        if(parent == null){//树为空
            root = new BSTNode(key,value);
            return;
        }
        //2
//        new BSTNode(key,value);
        if(key< parent.key){
            parent.left = new BSTNode(key,value);
        }else if(key> parent.key){//也可以直接改为else即可
            parent.right = new BSTNode(key,value);
        }
    }

前驱&后继

/**
     * <h3>查找关键字的前驱值</h3>
     * @param:key-关键字
     * @return:前驱值
     */
    //中序遍历结果就是升序的结果
    //但是我们实际去实现的时候并不会采用中序遍历 因为性能不高 最坏的情况两个指针要把整个树走一次
    /*
    前人的经验:
        分为有左子树时,此时前驱节点就是左子树的最大值,图中属于这种情况的有
          没有左子树时,若离它最近的祖先从左而来,此时祖先为前驱
     */
    public Object successor(int key) {
        BSTNode p = root;
        BSTNode ancestorFromLeft = null;
        while(p!=null){
            if(p.key < key){
                ancestorFromLeft = p;
                p = p.right;
            }else if(p.key > key){
                p = p.left;
            }else{
                break;
            }
        }
        //没找到节点的情况
        if(p==null){
            return null;
        }
        //找到节点
        if(p.left!=null){
            return max(p.left);
        }
        //情况二:节点没有左子树(自左而来)
        return ancestorFromLeft!=null?ancestorFromLeft.value:null;
    }

在这里也修改了一下max的代码

    /**
     * <h3>查找最大关键字对应值</h3>
     * @return:关键字对应值
     */
    public Object max(){
        return max(root);
    }
    //写的更通用 方便找前驱等使用
    private Object max(BSTNode node){
        if(node==null){
            return null;
        }
        BSTNode p = node;
        while(p.right!=null){
            p = p.right;
        }
        return p.value;
    }

后继

   /**
     * <h3>查找关键字的后继值</h3>
     * @param:key-关键字
     * @return:后继值
     */
    public Object predecessor(int key){
        BSTNode p = root;
        BSTNode ancestorFromRight = null;
        while(p!=null){
            if(p.key < key){
                p = p.right;
            }else if(p.key > key){
                ancestorFromRight = p;
                p = p.left;
            }else{
                break;
            }
        }
        //没找到节点的情况
        if(p==null){
            return null;
        }
        //找到节点
        if(p.right!=null){
            return min(p.right);
        }
        //情况二:节点没有右子树(自右而来)
        return ancestorFromRight!=null?ancestorFromRight.value:null;

    }
public Object min(){

        return min(root);
    }
    private Object min(BSTNode node){
        if(node==null){
            return null;
        }
        BSTNode P = node;
        while(P.left!=null){
            P = P.left;
        }
        return P.value;
    }

删除delete

第一种情况:没有左孩子

 情况二:没有右孩子

    /**
     * <h3>根据关键字删除</h3>
     * @param:key-关键字
     * @return:被删除关键字对应值
     */
    public Object delete(int key){
        //先找到节点
        BSTNode parent =null;//记录待删除节点的父亲
        BSTNode p = root;
        while(p!=null){
            if(key<p.key){
                parent = p;
                p = p.left;
            }else if(p.key < key){
                parent = p;
                p = p.right;
            }else{
                break;
            }
        }
        if(p==null){
            return null;
        }
        //找到了进行删除操作
        if(p.left==null && p.right!=null){
            //情况一:
            shift(parent,p,p.right);
        }else if(p.right==null&&p.left!=null){
            //情况二:
            shift(parent,p,p.left);
        }
        return p.value;
    }

    /**
     * 托孤方法
     * @param parent-被删除节点的父亲
     * @param deleted-被删除节点
     * @param child-被顶上去的节点
     */
    private void shift(BSTNode parent,BSTNode deleted,BSTNode child){
        if(parent==null){
            root = child;
        }else if(deleted==parent.left){
            parent.left = child;
        }else{
            parent.right =child;
        }
    }

情况三:直接走情况一或者情况二的逻辑即可 直接将if 和 else if && 后面那部分删了即可 

情况四: 后继相邻 & 后继不相邻(后继要先处理自己的后事)

public Object delete(int key){
        //先找到节点
        BSTNode parent =null;//记录待删除节点的父亲
        BSTNode p = root;
        while(p!=null){
            if(key<p.key){
                parent = p;
                p = p.left;
            }else if(p.key < key){
                parent = p;
                p = p.right;
            }else{
                break;
            }
        }
        if(p==null){
            return null;
        }
        //找到了进行删除操作
        if(p.left==null){
            //情况一:
            shift(parent,p,p.right);
        }else if(p.right==null){
            //情况二:
            shift(parent,p,p.left);
        }else{//情况四
            //4.1被删除节点的后继节点
            BSTNode s = p.right;
            BSTNode sParent=p;//后继父亲
            while(s.left!=null){
                sParent = s;
                s= s.left;
            }
            //后继节点即为s
            if(sParent!=p){
                //说明不相邻
                4.2删除节点和后继节点不相邻,要处理后继的后事
                shift(sParent,s,s.right);//不可能有左孩子 因为是后继
                s.right = p.right;
            }
            //4.3后继取代被删除节点
            shift(parent,p,s);
            s.left = p.left;

        }
        return p.value;
    }

整体代码:

/**
 * Binary Search Tree 二叉搜索树
 */
public class BSTTree1 {
    BSTNode root;//根节点
   static class BSTNode {
        int key;
        Object value;
        BSTNode left;
        BSTNode right;

        public BSTNode(int key){
            this.key = key;

        }

       public BSTNode(int key, Object value) {
           this.key = key;
           this.value = value;
       }

       public BSTNode(int key, Object value, BSTNode left, BSTNode right){
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
   }

    /**
     * <h3>查找关键字对应的值</h3>
     * @param:key-关键字
     * @return:关键字对应的值
     */

    //非递归版本
   public Object get(int key){
        BSTNode node =root;
        while(node!=null){
            if(key<node.key){
                node = node.left;
            }else if(key>node.key){
                node = node.right;
            }else{
                return node.value;
            }
        }
        return null;
   }
    /**
     * <h3>查找最小关键字对应值</h3>
     * @Returns:关键字对应的值
     */
//    public Object min(){
//        return  doMin(root);
//    }

    public Object min(){

        return min(root);
    }
    private Object min(BSTNode node){
        if(node==null){
            return null;
        }
        BSTNode P = node;
        while(P.left!=null){
            P = P.left;
        }
        return P.value;
    }

    public Object doMin(BSTNode node){
        if(node==null){
            return null;
        }
        if(node.left == null){//找到了最小的节点
            return node.value;
        }
        return doMin(node.left);//尾递归
    }

    /**
     * <h3>查找最大关键字对应值</h3>
     * @return:关键字对应值
     */
    public Object max(){
        return max(root);
    }
    //写的更通用 方便找前驱等使用
    private Object max(BSTNode node){
        if(node==null){
            return null;
        }
        BSTNode p = node;
        while(p.right!=null){
            p = p.right;
        }
        return p.value;
    }

    /**
     * <h3>存储关键字和对应值</h3>
     * @param:key 关键字
     * @param:value 值
     */
    public void put(int key,Object value){
        //1.key 有  更新
        //2.key 没有 更新
        BSTNode node = root;
        BSTNode parent =null;
        while(node!=null){
            parent = node;
            if(key <node.key){
                node = node.left;
            }else if(node.key<key){
                node=node.right;
            }else{
                //1
                node.value = value;
                return;
            }
        }
        if(parent == null){//树为空
            root = new BSTNode(key,value);
            return;
        }
        //2
//        new BSTNode(key,value);
        if(key< parent.key){
            parent.left = new BSTNode(key,value);
        }else if(key> parent.key){//也可以直接改为else即可
            parent.right = new BSTNode(key,value);
        }
    }

    /**
     * <h3>查找关键字的前驱值</h3>
     * @param:key-关键字
     * @return:前驱值
     */
    //中序遍历结果就是升序的结果
    //但是我们实际去实现的时候并不会采用中序遍历 因为性能不高 最坏的情况两个指针要把整个树走一次
    /*
    前人的经验:
        分为有左子树时,此时前驱节点就是左子树的最大值,图中属于这种情况的有
          没有左子树时,若离它最近的祖先从左而来,此时祖先为前驱
     */
    public Object successor(int key) {
        BSTNode p = root;
        BSTNode ancestorFromLeft = null;
        while(p!=null){
            if(p.key < key){
                ancestorFromLeft = p;
                p = p.right;
            }else if(p.key > key){
                p = p.left;
            }else{
                break;
            }
        }
        //没找到节点的情况
        if(p==null){
            return null;
        }
        //找到节点
        if(p.left!=null){
            return max(p.left);
        }
        //情况二:节点没有左子树(自左而来)
        return ancestorFromLeft!=null?ancestorFromLeft.value:null;
    }

    /**
     * <h3>查找关键字的后继值</h3>
     * @param:key-关键字
     * @return:后继值
     */
    public Object predecessor(int key){
        BSTNode p = root;
        BSTNode ancestorFromRight = null;
        while(p!=null){
            if(p.key < key){
                p = p.right;
            }else if(p.key > key){
                ancestorFromRight = p;
                p = p.left;
            }else{
                break;
            }
        }
        //没找到节点的情况
        if(p==null){
            return null;
        }
        //找到节点
        if(p.right!=null){
            return min(p.right);
        }
        //情况二:节点没有右子树(自右而来)
        return ancestorFromRight!=null?ancestorFromRight.value:null;

    }

    /**
     * <h3>根据关键字删除</h3>
     * @param:key-关键字
     * @return:被删除关键字对应值
     */
    public Object delete(int key){
        //先找到节点
        BSTNode parent =null;//记录待删除节点的父亲
        BSTNode p = root;
        while(p!=null){
            if(key<p.key){
                parent = p;
                p = p.left;
            }else if(p.key < key){
                parent = p;
                p = p.right;
            }else{
                break;
            }
        }
        if(p==null){
            return null;
        }
        //找到了进行删除操作
        if(p.left==null){
            //情况一:
            shift(parent,p,p.right);
        }else if(p.right==null){
            //情况二:
            shift(parent,p,p.left);
        }else{//情况四
            //4.1被删除节点的后继节点
            BSTNode s = p.right;
            BSTNode sParent=p;//后继父亲
            while(s.left!=null){
                sParent = s;
                s= s.left;
            }
            //后继节点即为s
            if(sParent!=p){
                //说明不相邻
                4.2删除节点和后继节点不相邻,要处理后继的后事
                shift(sParent,s,s.right);//不可能有左孩子 因为是后继
                s.right = p.right;
            }
            //4.3后继取代被删除节点
            shift(parent,p,s);
            s.left = p.left;

        }
        return p.value;
    }

    /**
     * 托孤方法
     * @param parent-被删除节点的父亲
     * @param deleted-被删除节点
     * @param child-被顶上去的节点
     */
    private void shift(BSTNode parent,BSTNode deleted,BSTNode child){
        if(parent==null){
            root = child;
        }else if(deleted==parent.left){
            parent.left = child;
        }else{
            parent.right =child;
        }
    }
}

递归实现delete

    

 /**
     * <h3>根据关键字删除</h3>
     *
     * @param:key-关键字
     * @return:被删除关键字对应值
     */
    public Object delete(int key) {
        ArrayList<Object>result = new ArrayList<>();//保存被删除节点的值
        root = doDelete(root,key,result);
        return result.isEmpty()?null:result.get(0);
    }

    /*
    递归版
    1.删除节点没有左孩子,将右孩子托孤给Parent
    2.删除节点没有右孩子,将左孩子托孤给Parent
    3.删除节点左右孩子都没有,已经被涵盖在情况一,情况二当中,把null托孤给Parent
    4.删除左右节点孩子都有,可以把它的后继节点(称之为S)托孤给parent
            4
           / \
          2   6
         /     \
        1       7
        node : 起点
        返回值: 删剩下的孩子节点 或 null
     */
    private BSTNode doDelete(BSTNode node, int key,ArrayList<Object>result) {
        if (node == null) {
            return null;
        }
        if (key < node.key) {
            node.left = doDelete(node.left, key,result);//假如要删除2 那么返回1
            return node;
        }
        if (node.key < key) {
            node.right = doDelete(node.right, key,result);
            return node;
        }
        //找到了
        result.add(node.value);
        //情况1: 只有右孩子
        if (node.left == null) {
            return node.right;//返回的就是删剩下的东西
        }
        //情况2: 只有左孩子
        if (node.right == null) {
            return node.left;//返回的就是删剩下的东西
        }
        //情况3: 两个孩子都有
        BSTNode s = node.right;///后继节点
        while (s.left != null) {
            s = s.left;
        }
        s.right = doDelete(node.right, s.key,new ArrayList<>());//反正这个new的用不上 因为是删除后继节点的
        s.left = node.left;
        return s;
    }

范围查询

/*
    范围查询
     */

    //中序遍历-->升序
    /*
            4
           / \
          2   6
         / \  /\
        1  3  5 7

     */

    //找 < key 的所有value
    public List<Object>less(int key){ //key = 6
        ArrayList<Object>result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while(p!=null||!stack.isEmpty()){
            if(p!=null){
                stack.push(p);
                p=p.left;
            }else{
                BSTNode pop = stack.pop();
                //处理值
                if(pop.key<key){
                    result.add(pop.value);
                }else{
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }



    //找>key的所有value
    /*
    Pre-order,NLR
    In-order,LNR
    Post-order,LRN
    Reverse Pre-order,NRL
    Reverse In-order,RNL
    Reverse post-order,RLN
     */
    public List<Object>greater(int key){ //优化 从后开始 反向中序遍历
//        ArrayList<Object>result = new ArrayList<>();
//        BSTNode p = root;
//        LinkedList<BSTNode> stack = new LinkedList<>();
//        while(p!=null||!stack.isEmpty()){
//            if(p!=null){
//                stack.push(p);
//                p=p.left;
//            }else{
//                BSTNode pop = stack.pop();
//                //处理值
//                if (key<pop.key){
//                    result.add(pop.value);
//                }
//                //不能提前结束 因为ta要把全部走一次
//                p = pop.right;
//
//
//            }
//        }
//        return result;


        ArrayList<Object>result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while(p!=null||!stack.isEmpty()){
            if(p!=null){
                stack.push(p);
                p=p.right;
            }else{
                BSTNode pop = stack.pop();
                //处理值
                if (key<pop.key){
                    result.add(pop.value);
                }
                else{
                    break;
                }
//                System.out.println(pop.value);//逆序

                p = pop.left;


            }
        }
        return result;
    }

    //找>=key1||<=key2的所有值
    public List<Object>between(int key1,int key2){
        ArrayList<Object>result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while(p!=null||!stack.isEmpty()){
            if(p!=null){
                stack.push(p);
                p=p.left;
            }else{
                BSTNode pop = stack.pop();
                //处理值
                if(pop.key>=key1 && pop.key<=key2){
                    result.add(pop.value);
                }else if(pop.key>key2){
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }

二叉搜索树练习

上面实现写过的就不做实现了.

删除节点-力扣450题

450. 删除二叉搜索树中的节点 - 力扣(LeetCode)

  • 当然要注意的是:题目中的树节点TreeNode相当于例题中的BSTNode
  • val,left,right(val即作为键又作为值)
  • key,value,left,right
  • Ta的TreeNode没有key,比较用的是TreeNode.val属性与待删除key进行比较
/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode deleteNode(TreeNode node, int key) {
        if(node==null){
            return null;
        }
        if(key<node.val){
            node.left = deleteNode(node.left,key);
            return node;
        }
        if(node.val<key){
            node.right = deleteNode(node.right,key);
            return node;
        }
        if(node.left==null){//情况一:只有右孩子
            return node.right;
        }
        if(node.right == null){//情况二:只有左孩子
            return node.left;
        }
        TreeNode s = node.right;//情况三:有两个孩子
        while(s.left != null){
            s= s.left;
        }
        s.right = deleteNode(node.right,s.val);
        s.left = node.left;
        return s;
    }
}

也可以这么写:

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode deleteNode(TreeNode root, int key) {
        if(root == null){
            return null;
        }
        if(key > root.val){
            root.right = deleteNode(root.right,key);//去右子树删除

        }
        else if(key<root.val){
            root.left = deleteNode(root.left,key);//去左子树删除

        }
        else{//当前节点就是要删除的节点
            if(root.left==null){
                return root.right;//情况一
            }
            if(root.right==null){
                return root.left;//情况二
            }
            TreeNode node = root.right;//情况三
            while(node.left!=null){//寻找要删除节点右子树的最左节点
                node = node.left;
            }
            node.left = root.left;
            //将要删除节点的左子树成为其右子树的最左节点的左子树
            root = root.right;//将要删除节点的右子顶替它的位置,节点被删除

        }
        return root;
    }
}

新增节点-力扣701

701. 二叉搜索树中的插入操作 - 力扣(LeetCode)

/**
 * 新增节点(题目前提:val一定与树中节点不同)
 */
public class Leetcode701 {

    /*
        put(key,value)
            - key 存在 更新
            - key 不存在 新增
     */

    /*
            val=7

            5
           / \
          2   6
         / \   \
        1   3   7
     */
    public TreeNode insertIntoBST(TreeNode node,int val){
        if(node==null){//找到空位
            return new TreeNode(val);
        }
        if(val<node.val){
            //2.left = 2 多余的赋值动作
            node.left = insertIntoBST(node.left,val);//val=1 建立父子关系
        }else if(node.val<val){
            node.right = insertIntoBST(node.right,val);
        }
        return node;
    }
}

查询节点-力扣700

700. 二叉搜索树中的搜索 - 力扣(LeetCode)

/**
 * Definition for a binary tree node.
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
        if(root==null){
            return null;
        }
        if(val<root.val){
            return searchBST(root.left,val);
        }else if(root.val<val){
            return searchBST(root.right,val);
        }else{
            return root;
        }
    }
}

验证二叉搜索树-力扣98

98. 验证二叉搜索树 - 力扣(LeetCode)

// 解法1:中序遍历非递归实现
    public boolean isValidBST(TreeNode node){
        TreeNode p = node;
        LinkedList<TreeNode> stack = new LinkedList<>();
        long prev=Long.MIN_VALUE;//代表上一个值
        while(p!=null || !stack.isEmpty()){
            if(p!=null){
                stack.push(p);
                p= p.left;
            }else{
                TreeNode pop = stack.pop();
                //处理值
                if(prev >= pop.val){
                    return false;
                }
                prev = pop.val;
                p = pop.right;
            }
        }
        return true;
    }
    
long pre = Long.MIN_VALUE;
    // 解法2:中序遍历递归实现
    public boolean isValidBST(TreeNode node){
        if(node==null){
            return true;
        }
        boolean a = isValidBST(node.left);
        //值
        if(pre >= node.val){
            return false;
        }
        pre = node.val;
        boolean b = isValidBST(node.right);
        return a&&b;
    }

虽然在leetcode上这段代码运行的效率比非递归的高 但是仍然不是最优效率的,下面来看一下:

上面这段代码到这里仍然没有结束

 

 

long pre = Long.MIN_VALUE;
    // 解法2:中序遍历递归实现
    public boolean isValidBST(TreeNode node){
        if(node==null){
            return true;
        }
        boolean a = isValidBST(node.left);
        if(!a){
            return false;//做一个提前的返回
        }
        //值
        if(pre >= node.val){
            return false;
        }
        pre = node.val;
        return isValidBST(node.right);;
    }

剪枝操作.

还有一个问题就是pre 变量的一个小坑:

  public boolean isValidBST(TreeNode node){
        return doValid(node,Long.MIN_VALUE);
    }

    private boolean doValid(TreeNode node,long prev){
        if(node==null){
            return true;
        }
        boolean a = doValid(node.left,prev);
        if(!a){
            return false;
        }
        if(prev>=node.val){
            return false;
        }
        prev = node.val;
        return doValid(node.right,prev);
        
        /*
                3
                 \ 
                  4
                 /
                5
        
         */
    }

这样写看似没有问题 但在运行像上面345这个实例的时候会发生问题 实际上当3往下走的时候4会先走doValid(node.left,prev); 这样看似prev = node.val 也就是 prev = 5,但因为你的prev是作为一个参数,是对整个方法中生效的,也就是4这个点有doValid(node.left,prev) 和 doValid(node.right,prev)这两个prev都是4这个点传入的prev并不会说因为在doValid(node.prev)中prev=5导致这个方法的所有prev都变为了5,本质也就是这个参数只是这个方法的局部变量,局部变量的修改不会影响到其他的方法

解决方法:1.prev作为全局变量(上面实现过)  2.AtomicLong prev也就是作为是一个对象

  public boolean isValidBST(TreeNode node){
        return doValid(node,new AtomicLong(Long.MIN_VALUE));
        //始终传递的都是同一个对象 所以修改可以改变其他方法中prev
        //那能不能用Long 这个长整型作为对象呢?不行,Long和其他java
        //里的包装类型都属于不可变数据类型,里面的值是不能修改的
        //AtomicLong是可变数据类型,里面的数据是可以用set修改
    }

    private boolean doValid(TreeNode node, AtomicLong prev){
        if(node==null){
            return true;
        }
        boolean a = doValid(node.left,prev);
        if(!a){
            return false;
        }
        if(prev.get()>=node.val){
            return false;
        }
        prev.set(node.val);
        return doValid(node.right,prev);
    }

上面的所有方法都是利用中序遍历判断,

能否只判断父亲比左孩子大比右孩子小?no 考虑少了

    /*
           能否只判断父亲比左孩子大,比右孩子小?
                4
              /    \
             2      6
           /  \
          1    3

    反例:
           4
         /   \
        2     6
             /  \
            3    7

只考虑了父子之间没有考虑祖先跟后代的大小关系,判断上就出现了疏漏
     */

可以用上下限的方法来解决:

/*

        -∞ 4  +∞
         /   \
      -∞ 2 4 4 6 +∞
             /  \
          4 3 6 6 7 +∞
     */
    //解法4:上下限递归实现 0ms
    public boolean isValidBST4(TreeNode node){
        return doValid4(node,Long.MIN_VALUE,Long.MAX_VALUE);
    }
    private boolean doValid4(TreeNode node,long min,long max){
        if(node==null){
            return true;
        }
        if(node.val<=min || node.val>=max){
            return false;
        }
        return doValid4(node.left,min,node.val) && doValid4(node.right,node.val,max);

    }

搜索二叉树范围和-力扣938

938. 二叉搜索树的范围和 - 力扣(LeetCode)

// 解法1: 中序非递归实现 4ms
    public int rangeSumBST(TreeNode node,int low,int high){
        TreeNode p  = node;
        LinkedList<TreeNode>stack = new LinkedList<>();
        int sum = 0;
        while(p!=null||!stack.isEmpty()){
            if(p!=null){
                stack.push(p);
                p=p.left;
            }else{
                TreeNode pop = stack.pop();
                //处理值
                if(pop.val>high){
                    break;
                }//剪枝

                if(pop.val >= low){
                    sum+=pop.val;
                }
                p = pop.right;
            }
        }
        return sum;
    }

解题思路
标签:深度优先遍历
题意:这个题字面含义很难理解,本意就是求出所有 X >= L 且 X <= R 的值的和
递归终止条件:
当前节点为 null 时返回 0
当前节点 X < L 时则返回右子树之和
当前节点 X > R 时则返回左子树之和
当前节点 X >= L 且 X <= R 时则返回:当前节点值 + 左子树之和 + 右子树之和

 //中序遍历性能不高的原因在于不能够有效的筛选 对high可以剪枝 但是对low没有办法有效的剪枝
    // 解法2:上下限递归
    /*
            10
           /  \
          5    15
         / \    \
        3   7    18     low=7   high=15

     */
    public int rangeSumBST(TreeNode node,int low,int high){
        if(node==null){
            return 0;
        }
        if(node.val<low){
            return rangeSumBST(node.right,low,high);
        }
        if(node.val>high){
            return rangeSumBST(node.left,low,high);
        }

        // 在范围内
        return node.val + rangeSumBST(node.left, low, high) + rangeSumBST(node.right, low, high);

    }

 

前序遍历构造二叉搜索树-力扣1008

/**
 * 根据前序遍历构造二叉搜索树 题目说明 preorder 长度>=1
 */
public class Leetcode1008 {
    /*
    1.preorder长度>=1
    2.preorder没有重复值

        8, 5, 1, 7, 10, 12

                8
               / \
              5  10
             / \   \
            1   7  12
     */
    public TreeNode bstFromPreorder(int[] preorder){
        TreeNode root = new TreeNode(preorder[0]);
        for (int i = 1; i < preorder.length; i++) {
            int val = preorder[i];
            insert(root,val);//n * log(n)
        }
        return root;
    }
    private TreeNode insert(TreeNode node,int val){
        if(node==null){
            return new TreeNode(val);
        }
        if(val<node.val){
            node.left = insert(node.left,val);
        }else if(node.val<val){
            node.right = insert(node.right,val);
        }//没有重复值 直接不写else
        return node;
    }
}

方法二: 

 // 上限法 8 5 1 7 10 12
    /*
    1.遍历数组中的每一个值,根据值创建节点
        - 每个人节点若成功创建:做孩子上限,右孩子上限
    2.处理下一个值时,如果超过此上限,那么
        - 将null作为上个节点的孩子
        - 不能创建节点对象
        - 直到不超过上限为止
    3.重复1,2步骤

     */
    public TreeNode bstFromPreorder(int[] preorder){
        return insert(preorder,Integer.MAX_VALUE);
    }
    /*
        依次处理 preorder 中每个值,返回创建好的节点或null
        1.如果超过上限,返回null,作为孩子返回
        2.如果没超过上限,创建节点,并设置其左右孩子
            左右孩子完整后返回
     */

    int i = 0;//索引从0开始
    private TreeNode insert(int[] preorder,int max){
        if(i==preorder.length){
            return null;
        }
        int val = preorder[i];
        if(val>max){
            return null;
        }
        //没有超过上限
        TreeNode node = new TreeNode(val);
        i++;//找到下一个值看看下一个值能不能作为左孩子或者右孩子
        node.left = insert(preorder,val);//左孩子上限就是自身的值
        node.right= insert(preorder,max);//
        return node;

    }

上下限:时间复杂度为O(n)

分治法:O(n*logn)

   //分治法
    /*
        8 5 1 7 10 12
       根 8
       左 5 1 7
       右 10 12

       根 5  左 1 右 7
       根 10 左 null 右 12

    */

    public TreeNode bstFromPreorder1(int[] preorder){
        return partition(preorder,0,preorder.length);
    }
    private TreeNode partition(int[] preorder,int start,int end){
        if(start>end){
            return null;
        }
        TreeNode root = new TreeNode(preorder[start]);
        int index = start+1;
        while(index<=end){
            if(preorder[index]>preorder[start]){
                break;
            }
            index++;
        }
        //index是右子树的起点
        root.left = partition(preorder,start+1,index-1);
        root.right = partition(preorder,index,end);
        return root;
    }

求二叉搜索树最近公共祖先(祖先也包括自己) - 力扣235

/**
 * 求二叉搜索树最近公共祖先(祖先也包括自己)
 * 前提:
 * <ul>
 *     <li>节点值唯一</li>
 *     <li>p和q都存在</li>
 * </ul>
 */
public class Leetcode235 {
    /*
           _ _ 6_ _
          /         \
         2           8
        / \         / \
       0   4       7   9
          / \
         3   5

         待查找节点p q 在某一节点的两侧,那么此节点就是最近的公共祖先
     */
    public TreeNode lowestCommonAncestor(TreeNode root,TreeNode p,TreeNode q){
        TreeNode a = root;
        while(p.val<a.val&&q.val<a.val || a.val<p.val && a.val<q.val){ //在同一侧
            if(p.val<a.val){
                a = a.left;
            }else{
                a= a.right;
            }
        }
        return a;
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/561166.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自然语言处理基础面试

文章目录 TF-IDFbag-of-wordsBert 讲道理肯定还得有Transformer&#xff0c;我这边先放着&#xff0c;以后再加吧。 TF-IDF TF&#xff08;全称TermFrequency&#xff09;&#xff0c;中文含义词频&#xff0c;简单理解就是关键词出现在网页当中的频次。 IDF&#xff08;全称…

【C++初识继承】

博主首页&#xff1a; 有趣的中国人 专栏首页&#xff1a; C进阶 本篇文章主要讲解 继承 的相关内容 目录 1. 继承的概念和定义 1.1 继承的概念 1.2 继承的定义 1.2.1 继承定义格式 1.2.2 继承方式与访问修饰限定符 2. 基类和派生类对象赋值转换 3. 继承中的作用域 …

读天才与算法:人脑与AI的数学思维笔记05_算法的幻觉

1. 自下而上 1.1. 代码在未来可以自主学习、适应并进行自我改进 1.2. 程序员通过编程教会计算机玩游戏&#xff0c;而计算机却会比教它的人玩得更好&#xff0c;这种输入寡而输出众的事情不大可能实现 1.3. 早在20世纪50年代&#xff0c;计算机科学家们就模拟该过程创造了感…

内存概念理解:RANK,BANK,BURST,INTERLEAVING

背景&#xff1a;死磕内存的bank和rank概念的一天。网上的资料都差不多&#xff0c;还是有些地方没理通顺&#xff0c;有什么内存基础知识的书籍可以推荐吗&#xff1f; 物理RANK的概念 当我们给计算机购买内存条时候&#xff0c;上面显示的1RX8, 2RX8&#xff0c;其中R就是r…

【ARM 裸机】I.MX 启动方式之启动头文件 1

接上一节&#xff1a;【ARM 裸机】I.MX 启动方式之启动设备的选择&#xff1b; 2、启动头文件 当 BOOT_MODE1 为 1&#xff0c;BOOT_MODE0 为 0 的时候此内部 BOOT 模式&#xff0c;在此模式下&#xff0c;芯片会执 行内部的 BOOT ROM 代码&#xff0c;这段 BOOT ROM 代码会进…

常见的七种排序

目录 一、插入排序 1、直接插入排序 2、希尔排序&#xff08;缩小增量排序&#xff09; 二、选择排序 3、直接选择排序 4、堆排序 三、交换排序 5、冒泡排序 6、快速排序 四、归并排序 7、归并排序 五、总结 一、插入排序 1、直接插入排序 思路&#xff1a; i 用来…

Xamarin.Android中“ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”异常处理

这里写自定义目录标题 1、问题2、解决 1、问题 在Xamarin.Android中出现ADB0020: Android ABI 不匹配。你正将应用支持的“armeabi-v7a;arm64-v8a”ABI 部署到 ABI“x86_64;x86”的不兼容设备。应创建匹配其中一个应用 ABI 的仿真程序&#xff0c;或将“x86_64”添加到应用生成…

Parade Series - CoreAudio Loopback

Scenario 鉴于业务场景需要&#xff0c; 经过技术路径探索&#xff0c; 发现 comtypes 兼容性过于混乱&#xff0c;故而考虑整合一个 CoreAudio 的轮子dll来解决实际问题&#xff01;std::StringStream ⇒ std::ios::binary ⇒ std::ofstream Loopback.dll #ifndef _DLL_C…

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available

【nvm最新解决方案】Node.js v16.20.2 is not yet released or available 解决办法&#xff1a;下载想安装的node压缩包&#xff0c;放入nvm对应目录。 2024年最新node压缩包地址&#xff1a;https://nodejs.org/dist/ 1、选择对应的node版本&#xff1a;例如&#xff0c;我选的…

如何创建响应式HTML电子邮件模板

在这个适合初学者的指南中&#xff0c;你将学习如何创建一个响应式电子邮件模板。你将跟随逐步说明以及代码片段设计一个在任何设备上都看起来很棒的电子邮件模板。 这个项目非常适合渴望掌握电子邮件设计基础的新手&#xff01; &#xff08;本文视频讲解&#xff1a;java56…

怎么用手机远程控制电脑 远程控制怎么用

怎么用手机远程控制电脑&#xff1a;远程控制怎么用 在这个科技日新月异的时代&#xff0c;远程控制电脑已经成为了很多人的需求。有时&#xff0c;我们可能在外出时突然需要访问家中的电脑&#xff0c;或者在工作中需要远程操控办公室的电脑。这时&#xff0c;如果能用手机远…

Spring 声明式事务控制

1. 编程式事务控制相关对象 1.1 PlatformTransactionManager PlatformTransactionManager 接口是 spring 的事务管理器&#xff0c;它提供了我们常用的操作事务的方法。 PlatformTransactionManager 是接口类型&#xff0c;不同的 Dao 层技术则有不同的实现类。例如:Dao层技…

【Spring】Spring源码中占位符解析器PropertyPlaceholderHelper的使用

开发中经常需要使用到占位符&#xff0c;情况较为复杂时总是手工替换处理显得比较的繁琐&#xff0c;加之往往手工所写效率比不上框架自带的现有方法来的更好更快。Spring在处理yml配置文件时&#xff0c;对于yml文件名的占位符替换处理便是使用了占位符解析器PropertyPlacehol…

深入了解PBKDF2:密码学中的关键推导函数

title: 深入了解PBKDF2&#xff1a;密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学对称加密哈希函数KDFPBKDF2安全密钥派生 第一章&#xff1a;密码学基础 对称加密和哈希函数 对称加密&#xff1a;对称加密是一种加密技术&…

Windows COM技术:COM介绍、代码演示。

目录 步骤一&#xff1a;理解COM技术 介绍COM的基础知识 1. COM的目的和特点 2. COM的关键概念 3. COM的实现 4. COM与DCOM、ActiveX 讨论COM的用途 1. 软件自动化 2. 插件和扩展 3. 跨语言开发 4. 分布式计算 5. 系统级组件 6. 网络浏览器插件 步骤二&#xff1a…

开源贡献代码之​探索一下CPython

探索一下Cython 本篇文章将会围绕最近给Apache提的一个feature为背景&#xff0c;展开讲讲CPython遇到的问题&#xff0c;以及尝试自己从0写一个库出来&#xff0c;代码也已经放星球了&#xff0c;感兴趣的同学可以去下载学习。 0.背景 最近在给apache arrow提的一个feature因为…

【做一名健康的CSDNer】程序员如何早日脱单?

程序员脱单的策略可以从以下几个方面着手&#xff1a; 拓展社交圈&#xff1a;参加技术交流会、行业聚会、开源社区活动等&#xff0c;不仅可以提升技术能力&#xff0c;还可以结识更多志同道合的人&#xff0c;其中可能就包括潜在的伴侣65。 改善形象和性格&#xff1a;注意个…

【GIS教程】ArcGIS做日照分析(附练习数据下载)

我国对住宅日照标准的规定是:冬至日住宅底层日照不少于1小时或大寒日住宅层日照不少于2小时(通常以当地冬至日正午12时的太阳高度角作为依据)。因冬至日太阳高度角最低&#xff0c;照射范围最小&#xff0c;如果冬至日12&#xff1a;00建筑物底层能够接收到阳光&#xff0c;那么…

探索边缘计算:技术的新疆界

探索边缘计算&#xff1a;技术的新疆界 在当今迅速发展的数字化时代&#xff0c;云计算作为数据处理的主力军已广泛应用。但是&#xff0c;随着物联网&#xff08;IoT&#xff09;设备的急剧增加和数据生成速率的加快&#xff0c;云计算面临着种种挑战。边缘计算因此诞生&…

python爬虫-----深入了解 requests 库下篇(第二十五天)

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…