当前位置:   article > 正文

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

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

二叉树

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

重要的二叉树结构

  • 完全二叉树(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 这种方式表示的二叉树

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

深度优先

 

 递归

  1. public class TreeTraversal {
  2. public static void main(String[] args) {
  3. /*
  4. 1
  5. / \
  6. 2 3
  7. / /\
  8. 4 5 6
  9. */
  10. TreeNode root = new TreeNode(
  11. new TreeNode(new TreeNode(4),2,null),
  12. 1,
  13. new TreeNode(new TreeNode(5),3,new TreeNode(6))
  14. );
  15. preOrder(root);//1 2 4 3 5 6
  16. System.out.println();
  17. inOrder(root);//4 2 1 5 3 6
  18. System.out.println();
  19. PostOrder(root);
  20. System.out.println();
  21. }
  22. /**
  23. * 前序遍历
  24. * @Params:node-节点
  25. */
  26. static void preOrder(TreeNode node){
  27. if(node==null){
  28. return;
  29. }
  30. System.out.print(node.val+"\t");//当前节点值
  31. preOrder(node.left);// 左
  32. preOrder(node.right);// 右
  33. }
  34. /**
  35. * 中序遍历
  36. * @Params:node-节点
  37. */
  38. static void inOrder(TreeNode node){
  39. if(node==null){
  40. return;
  41. }
  42. inOrder(node.left); //左
  43. System.out.print(node.val+"\t");//值
  44. inOrder(node.right);//右
  45. }
  46. /**
  47. * 后序遍历
  48. * @Params:node-节点
  49. */
  50. static void PostOrder(TreeNode node){
  51. if(node==null){
  52. return;
  53. }
  54. PostOrder(node.left);
  55. PostOrder(node.right);
  56. System.out.print(node.val+"\t");
  57. }
  58. }

非递归

非递归实现

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

  1. LinkedListStack<TreeNode> stack = new LinkedListStack<>();
  2. TreeNode curr = root;
  3. while (!stack.isEmpty() || curr != null) {
  4.    if (curr != null) {
  5.        stack.push(curr);
  6.        System.out.println(curr);
  7.        curr = curr.left;
  8.   } else {
  9.        TreeNode pop = stack.pop();
  10.        curr = pop.right;
  11.   }
  12. }

中序遍历

  1. LinkedListStack<TreeNode> stack = new LinkedListStack<>();
  2. TreeNode curr = root;
  3. while (!stack.isEmpty() || curr != null) {
  4.    if (curr != null) {
  5.        stack.push(curr);
  6.        curr = curr.left;
  7.   } else {
  8.        TreeNode pop = stack.pop();
  9.        System.out.println(pop);
  10.        curr = pop.right;
  11.   }
  12. }

后序遍历

  1. LinkedListStack<TreeNode> stack = new LinkedListStack<>();
  2. TreeNode curr = root;
  3. TreeNode pop = null;
  4. while (!stack.isEmpty() || curr != null) {
  5.    if (curr != null) {
  6.        stack.push(curr);
  7.        curr = curr.left;
  8.   } else {
  9.        TreeNode peek = stack.peek();
  10.        if (peek.right == null || peek.right == pop) {
  11.            pop = stack.pop();
  12.            System.out.println(pop);
  13.       } else {
  14.            curr = peek.right;
  15.       }
  16.   }
  17. }

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

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

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

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

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

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

统一写法

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

  1. LinkedList<TreeNode> stack = new LinkedList<>();
  2. TreeNode curr = root; // 代表当前节点
  3. TreeNode pop = null; // 最近一次弹栈的元素
  4. while (curr != null || !stack.isEmpty()) {
  5.    if (curr != null) {
  6.        colorPrintln("前: " + curr.val, 31);
  7.        stack.push(curr); // 压入栈,为了记住回来的路
  8.        curr = curr.left;
  9.   } else {
  10.        TreeNode peek = stack.peek();
  11.        // 右子树可以不处理, 对中序来说, 要在右子树处理之前打印
  12.        if (peek.right == null) {
  13.            colorPrintln("中: " + peek.val, 36);
  14.            pop = stack.pop();
  15.            colorPrintln("后: " + pop.val, 34);
  16.       }
  17.        // 右子树处理完成, 对中序来说, 无需打印
  18.        else if (peek.right == pop) {
  19.            pop = stack.pop();
  20.            colorPrintln("后: " + pop.val, 34);
  21.       }
  22.        // 右子树待处理, 对中序来说, 要在右子树处理之前打印
  23.        else {
  24.            colorPrintln("中: " + peek.val, 36);
  25.            curr = peek.right;
  26.       }
  27.   }
  28. }
  29. public static void colorPrintln(String origin, int color) {
  30.    System.out.printf("\033[%dm%s\033[0m%n", color, origin);
  31. }

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

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

练习

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

代码实现:

  1. public class E04Leetcode101 {
  2. public boolean isSymmetric(TreeNode root){
  3. return check(root.left,root.right);
  4. }
  5. private boolean check(TreeNode left, TreeNode right) {
  6. if(left==null&&right==null){
  7. return true;
  8. }
  9. //执行到这里说明:left 和 right不能同时为null
  10. if(right==null||left==null){
  11. return false;
  12. }
  13. if(left.val!=right.val){
  14. return false;
  15. }
  16. return check(left.left,right.right) && check(left.right,right.left);
  17. }
  18. }

测试类:

  1. import org.junit.Test;
  2. import static org.junit.Assert.assertTrue;
  3. public class TestE04Leetcode101 {
  4. @Test
  5. public void test1(){
  6. TreeNode root = new TreeNode(
  7. new TreeNode(new TreeNode(3),2,new TreeNode(4)),
  8. 1,
  9. new TreeNode(new TreeNode(4),2,new TreeNode(3))
  10. );
  11. assertTrue(new E04Leetcode101().isSymmetric(root));
  12. }
  13. @Test
  14. public void test2(){
  15. TreeNode root = new TreeNode(
  16. new TreeNode(null,2,new TreeNode(3)),
  17. 1,
  18. new TreeNode(null,2,new TreeNode(3))
  19. );
  20. assertTrue(new E04Leetcode101().isSymmetric(root));
  21. }
  22. }

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

分析:

  1. /**
  2. * 二叉树最大深度-使用后序遍历求解
  3. */
  4. public class Leetcode104_1 {
  5. /*
  6. 思路:
  7. 1.得到左子树深度,得到右子树深度,二者最大值+1就是本节点深度
  8. 2.因为需要先得到左右子树深度,很显然是后序遍历典型应用
  9. 3.关于深度的定义:从根出发,离根最远的节点总边数
  10. 注意:力扣里的深度定义要多一
  11. 深度2 深度3 深度1
  12. 1 1 1
  13. / \ / \
  14. 2 3 2 3
  15. \
  16. 4
  17. */
  18. public int maxDepth(TreeNode node){
  19. if(node==null){
  20. return 0;
  21. }
  22. if(node.left==null&&node.right==null){
  23. return 1;
  24. }
  25. int d1 = maxDepth(node.left);
  26. int d2 = maxDepth(node.right);
  27. return Integer.max(d1,d2)+1;
  28. }
  29. }

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

第二种解法:

  1. import java.util.LinkedList;
  2. /**
  3. * 二叉树最大深度 - 使用后序遍历(非递归)求解
  4. */
  5. public class E05Leetcode104_2 {
  6. /*
  7. 思路:
  8. 1.使用非递归后序遍历,栈的最大高度即为最大深度
  9. */
  10. public int maxDepth(TreeNode root){
  11. TreeNode curr = root;
  12. TreeNode pop = null;
  13. LinkedList<TreeNode>stack = new LinkedList<>();
  14. int max = 0;//栈的最大高度
  15. while(curr!=null || !stack.isEmpty()){
  16. if(curr!=null){
  17. stack.push(curr);
  18. int size = stack.size();
  19. if(size>max){
  20. max = size;
  21. }
  22. curr=curr.left;
  23. }else{
  24. TreeNode peek = stack.peek();
  25. if(peek.right==null||peek.right==pop){
  26. pop=stack.pop();
  27. }else{
  28. curr=peek.right;
  29. }
  30. }
  31. }
  32. return max;
  33. }
  34. }

第三张方法:

 代码实现:

  1. public class E05Leetcode104_3 {
  2. /*
  3. 思路:
  4. 1.使用层序遍历,层数即最大深度
  5. */
  6. public int maxDepth(TreeNode root){
  7. Queue<TreeNode> queue= new LinkedList<>();
  8. //LinkedList:可以作为双向链表,队列,栈 "身兼数职"
  9. queue.offer(root);
  10. int depth=0;
  11. // int c1=1;//记录每层有几个元素
  12. while(!queue.isEmpty()){
  13. //queue.size()
  14. // int c2=0;
  15. int size = queue.size();
  16. for(int i=0;i<size;i++){
  17. TreeNode poll = queue.poll();
  18. // System.out.print(poll.val+"\t");
  19. if(poll.left!=null){
  20. queue.offer(poll.left);
  21. // c2++;
  22. }
  23. if(poll.right!=null){
  24. queue.offer(poll.right);
  25. // c2++;
  26. }
  27. }
  28. // c1=c2;
  29. // System.out.println();
  30. depth++;
  31. }
  32. return depth;
  33. }
  34. }
  1. if(root==null){
  2. return 0;
  3. }
  4. //有可能传过来的是null

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

  1. class Solution {
  2. public int minDepth(TreeNode root) {
  3. if(root==null)return 0;
  4. int m1 = minDepth(root.left);
  5. int m2 = minDepth(root.right);
  6. return root.left==null||root.right==null?m1+m2+1:Math.min(m1,m2)+1;
  7. }
  8. }
  1. class Solution {
  2. public int minDepth(TreeNode root){
  3. if(root == null){
  4. return 0;
  5. }
  6. Queue<TreeNode>queue = new LinkedList<>();
  7. queue.offer(root);
  8. int depth = 0;
  9. while(!queue.isEmpty()){
  10. int size = queue.size();
  11. depth++;
  12. for(int i=0;i<size;i++){
  13. TreeNode poll = queue.poll();
  14. if(poll.right==null&&poll.left==null){
  15. return depth;
  16. }
  17. if(poll.left!=null){
  18. queue.offer(poll.left);
  19. }
  20. if(poll.right!=null){
  21. queue.offer(poll.right);
  22. }
  23. }
  24. }
  25. return depth;
  26. }
  27. }

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

  1. class Solution {
  2. public TreeNode invertTree(TreeNode root) {
  3. fn(root);
  4. return root;
  5. }
  6. private void fn(TreeNode node){
  7. if(node==null){
  8. return;
  9. }
  10. TreeNode t = node.left;
  11. node.left = node.right;
  12. node.right = t;
  13. fn(node.left);
  14. fn(node.right);
  15. }
  16. }

  1. import java.util.LinkedList;
  2. /**
  3. * 根据后缀表达式构造表达式树
  4. */
  5. public class ExpressionTree {
  6. public static class TreeNode {
  7. public String val;
  8. public TreeNode left;
  9. public TreeNode right;
  10. public TreeNode(String val) {
  11. this.val = val;
  12. }
  13. public TreeNode(TreeNode left, String val, TreeNode right) {
  14. this.val = val;
  15. this.left = left;
  16. this.right = right;
  17. }
  18. @Override
  19. public String toString() {
  20. return this.val;
  21. }
  22. }
  23. /*
  24. 中缀表达式 (2-1)*3
  25. 后缀(逆波兰)表达式 21-3*
  26. 1.遇到数字入栈
  27. 2.遇到运算符出栈,建立节点关系,再入栈
  28. / /
  29. / /
  30. / /
  31. ----
  32. '-'.right = 1
  33. '-'.left = 2
  34. '*' .right = 3
  35. '*' .left = '-'
  36. 表达式树
  37. *
  38. / \
  39. - 3
  40. / \
  41. 2 1
  42. */
  43. public TreeNode constructExpressionTree(String[] tokens) {
  44. LinkedList<TreeNode> stack = new LinkedList<>();
  45. for (String t : tokens) {
  46. switch (t) {
  47. case "+", "-", "*", "/" -> {//运算符
  48. TreeNode right = stack.pop();
  49. TreeNode left = stack.pop();
  50. TreeNode parent = new TreeNode(t);
  51. parent.left = left;
  52. parent.right = right;
  53. stack.push(parent);
  54. }
  55. default -> {//数字
  56. stack.push(new TreeNode(t));
  57. }
  58. }
  59. }
  60. return stack.peek();
  61. //做个后序遍历 检测
  62. }
  63. }
  1. import Tree.ExpressionTree;
  2. import org.junit.Test;
  3. import java.util.ArrayList;
  4. import static org.junit.Assert.assertArrayEquals;
  5. public class TestExpressionTree {
  6. ExpressionTree exp = new ExpressionTree();
  7. @Test
  8. public void test1(){
  9. String[] tokens = {"2","1","-","3","*"};
  10. ExpressionTree.TreeNode root = exp.constructExpressionTree(tokens);
  11. ArrayList<String>result = new ArrayList<>();
  12. post(root,result);
  13. assertArrayEquals(tokens,result.toArray(new String[0]));
  14. }
  15. private void post(ExpressionTree.TreeNode node,ArrayList<String>result){
  16. if(node==null){
  17. return;
  18. }
  19. post(node.left,result);
  20. post(node.right,result);
  21. result.add(node.val);
  22. }
  23. }

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

  1. import java.util.Arrays;
  2. /**
  3. * 根据前中序遍历结果构造二叉树
  4. */
  5. public class Leetocde105 {
  6. /*
  7. preOrder = {1,2,4,3,6,7};
  8. inOrder = {4,2,1,6,3,7};
  9. 根1
  10. pre in
  11. 左 2 4 4,2
  12. 右 3,6,7 6,3,7
  13. 根 2
  14. 左 4
  15. 根 3
  16. 左 6
  17. 右 7
  18. */
  19. public TreeNode buildTree(int[] preOrder,int[] inOrder){
  20. if(preOrder.length==0||inOrder.length==0){
  21. return null;
  22. }
  23. //创建根节点
  24. int rootValue = preOrder[0];
  25. TreeNode root = new TreeNode(rootValue);
  26. //区分左右子树
  27. for (int i = 0; i < inOrder.length; i++) {
  28. if(inOrder[i]==rootValue){
  29. //0 ~ i-1 左子树
  30. //i+1 ~ inOrder.length-1 右子树
  31. int[] inLeft = Arrays.copyOfRange(inOrder, 0, i);//i是不包含的 [4,2]
  32. int[] inRight= Arrays.copyOfRange(inOrder, i, inOrder.length);//[6,3,7]
  33. int[] preLeft = Arrays.copyOfRange(preOrder, 1, i + 1);//[2,4]
  34. int[] preRight = Arrays.copyOfRange(preOrder, i+1, inOrder.length);//[3,6,7]
  35. root.left = buildTree(preLeft,inLeft); //2
  36. root.right = buildTree(preRight,inRight);//3
  37. break;
  38. }
  39. }
  40. return root;
  41. }
  42. }

参考力扣题解:

  1. 可以将中序遍历的值和索引存在一个哈希表中,这样就可以一下子找到根结点在中序遍历数组中的索引。
  2. Java
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. class TreeNode {
  6. int val;
  7. TreeNode left;
  8. TreeNode right;
  9. TreeNode(int x) {
  10. val = x;
  11. }
  12. }
  13. public class Solution {
  14. private int[] preorder;
  15. private Map<Integer, Integer> hash;
  16. public TreeNode buildTree(int[] preorder, int[] inorder) {
  17. int preLen = preorder.length;
  18. int inLen = inorder.length;
  19. if (preLen != inLen) {
  20. throw new RuntimeException("Incorrect input data.");
  21. }
  22. this.preorder = preorder;
  23. this.hash = new HashMap<>();
  24. for (int i = 0; i < inLen; i++) {
  25. hash.put(inorder[i], i);
  26. }
  27. return buildTree(0, preLen - 1, 0, inLen - 1);
  28. }
  29. private TreeNode buildTree(int preLeft, int preRight, int inLeft, int inRight) {
  30. // 因为是递归调用的方法,按照国际惯例,先写递归终止条件
  31. if (preLeft > preRight || inLeft > inRight) {
  32. return null;
  33. }
  34. // 先序遍历的起点元素很重要
  35. int pivot = preorder[preLeft];
  36. TreeNode root = new TreeNode(pivot);
  37. int pivotIndex = hash.get(pivot);
  38. root.left = buildTree(preLeft + 1, pivotIndex - inLeft + preLeft,
  39. inLeft, pivotIndex - 1);
  40. root.right = buildTree(pivotIndex - inLeft + preLeft + 1, preRight,
  41. pivotIndex + 1, inRight);
  42. return root;
  43. }
  44. }
  45. 复杂度分析:
  46. 时间复杂度:O(N)O(N)O(N),这里 NNN 是二叉树的结点个数,每调用一次递归方法创建一个结点,一共创建 NNN 个结点,这里不计算递归方法占用的时间。
  47. 空间复杂度:O(N)O(N)O(N),这里忽略递归方法占用的空间,因为是对数级别的,比 NNN 小。
  48. 作者:liweiwei1419
  49. 链接: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/
  50. 来源:力扣(LeetCode)
  51. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

  1. class Solution {
  2. public TreeNode buildTree(int[] inorder, int[] postorder) {
  3. if(inorder.length==0||postorder.length==0){
  4. return null;
  5. }
  6. int rootValue = postorder[postorder.length-1];
  7. TreeNode root = new TreeNode(rootValue);
  8. for(int i=0;i<inorder.length;i++){
  9. if(inorder[i] == rootValue){
  10. int[] inLeft = Arrays.copyOfRange(inorder,0,i);
  11. int[] inRight =Arrays.copyOfRange(inorder,i+1,inorder.length);
  12. int[] postLeft = Arrays.copyOfRange(postorder,0,i);
  13. int[] postRight = Arrays.copyOfRange(postorder,i,postorder.length-1);
  14. root.left = buildTree(inLeft,postLeft);
  15. root.right = buildTree(inRight,postRight);
  16. }
  17. }
  18. return root;
  19. }
  20. }

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

之前学过二分查找

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

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

二叉搜索树

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

get方法的实现

  1. /**
  2. * Binary Search Tree 二叉搜索树
  3. */
  4. public class BSTTree1 {
  5. BSTNode root;//根节点
  6. static class BSTNode {
  7. int key;
  8. Object value;
  9. BSTNode left;
  10. BSTNode right;
  11. public BSTNode(int key){
  12. this.key = key;
  13. }
  14. public BSTNode(int key, Object value) {
  15. this.key = key;
  16. this.value = value;
  17. }
  18. public BSTNode(int key, Object value, BSTNode left, BSTNode right){
  19. this.key = key;
  20. this.value = value;
  21. this.left = left;
  22. this.right = right;
  23. }
  24. }
  25. /**
  26. * <h3>查找关键字对应的值</h3>
  27. * @param:key-关键字
  28. * @return:关键字对应的值
  29. */
  30. public Object get(int key){
  31. return doGet(root,key);
  32. }
  33. //递归版本
  34. //尾递归 最后一步调用自身的时候(不含任何其他数) 转换成非递归实现性能更好 因为java不会对尾递归进行优化
  35. private Object doGet(BSTNode node,int key){
  36. if(node == null){
  37. return null;//没有找到
  38. }
  39. if(key<node.key){
  40. return doGet(node.left,key);//向左找
  41. }else if(node.key < key){
  42. return doGet(node.right,key);//向右找
  43. }else{
  44. return node.value;//找到了
  45. }
  46. }
  47. //非递归版本
  48. public Object get(int key){
  49. BSTNode node =root;
  50. while(node!=null){
  51. if(key<node.key){
  52. node = node.left;
  53. }else if(key>node.key){
  54. node = node.right;
  55. }else{
  56. return node.value;
  57. }
  58. }
  59. return null;
  60. }

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

  1. /*
  2. //泛型有没有比较大小的能力呢? 泛型中也有一个语法叫做泛型上限
  3. 将来我的泛型必须是Comparable的子类型
  4. */
  5. public class BSTNode2<T extends Comparable<T>,V> {
  6. static class BSTNode<T,V> {
  7. T key;
  8. V value;
  9. BSTNode<T,V> left;
  10. BSTNode<T,V> right;
  11. public BSTNode(T key){
  12. this.key = key;
  13. }
  14. public BSTNode(T key, V value) {
  15. this.key = key;
  16. this.value = value;
  17. }
  18. public BSTNode(T key, V value, BSTNode<T,V> left, BSTNode<T,V> right){
  19. this.key = key;
  20. this.value = value;
  21. this.left = left;
  22. this.right = right;
  23. }
  24. }
  25. BSTNode<T,V> root;
  26. /**
  27. * <h3>查找关键字对应的值</h3>
  28. * @param:key-关键字
  29. * @return:关键字对应的值
  30. */
  31. public V get(T key){
  32. BSTNode<T,V> p =root;
  33. while(p!=null){
  34. //key p.key
  35. /*
  36. -1 : key<p.key
  37. 0 : key=p.key
  38. 1 : key>p.key
  39. */
  40. int result = key.compareTo(p.key);
  41. if(result<0){
  42. p = p.left;
  43. }else if (result>0){
  44. p = p.right;
  45. }else{
  46. return p.value;
  47. }
  48. }
  49. return null;
  50. }

min-max的实现

  1. /**
  2. * <h3>查找最小关键字对应值</h3>
  3. * @Returns:关键字对应的值
  4. */
  5. // public Object min(){
  6. // return doMin(root);
  7. // }
  8. public Object min(){
  9. if(root==null){
  10. return null;
  11. }
  12. BSTNode P = root;
  13. while(P.left!=null){
  14. P = P.left;
  15. }
  16. return P.value;
  17. }
  18. public Object doMin(BSTNode node){
  19. if(node==null){
  20. return null;
  21. }
  22. if(node.left == null){//找到了最小的节点
  23. return node.value;
  24. }
  25. return doMin(node.left);//尾递归
  26. }
  27. /**
  28. * <h3>查找最大关键字对应值</h3>
  29. * @return:关键字对应值
  30. */
  31. public Object max(){
  32. if(root==null){
  33. return null;
  34. }
  35. BSTNode p = root;
  36. while(p.right!=null){
  37. p = p.right;
  38. }
  39. return p.value;
  40. }

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

  1. /**
  2. * <h3>存储关键字和对应值</h3>
  3. * @param:key 关键字
  4. * @param:value 值
  5. */
  6. public void put(int key,Object value){
  7. //1.key 有 更新
  8. //2.key 没有 更新
  9. BSTNode node = root;
  10. BSTNode parent =null;
  11. while(node!=null){
  12. parent = node;
  13. if(key <node.key){
  14. node = node.left;
  15. }else if(node.key<key){
  16. node=node.right;
  17. }else{
  18. //1
  19. node.value = value;
  20. return;
  21. }
  22. }
  23. if(parent == null){//树为空
  24. root = new BSTNode(key,value);
  25. return;
  26. }
  27. //2
  28. // new BSTNode(key,value);
  29. if(key< parent.key){
  30. parent.left = new BSTNode(key,value);
  31. }else if(key> parent.key){//也可以直接改为else即可
  32. parent.right = new BSTNode(key,value);
  33. }
  34. }

前驱&后继

  1. /**
  2. * <h3>查找关键字的前驱值</h3>
  3. * @param:key-关键字
  4. * @return:前驱值
  5. */
  6. //中序遍历结果就是升序的结果
  7. //但是我们实际去实现的时候并不会采用中序遍历 因为性能不高 最坏的情况两个指针要把整个树走一次
  8. /*
  9. 前人的经验:
  10. 分为有左子树时,此时前驱节点就是左子树的最大值,图中属于这种情况的有
  11. 没有左子树时,若离它最近的祖先从左而来,此时祖先为前驱
  12. */
  13. public Object successor(int key) {
  14. BSTNode p = root;
  15. BSTNode ancestorFromLeft = null;
  16. while(p!=null){
  17. if(p.key < key){
  18. ancestorFromLeft = p;
  19. p = p.right;
  20. }else if(p.key > key){
  21. p = p.left;
  22. }else{
  23. break;
  24. }
  25. }
  26. //没找到节点的情况
  27. if(p==null){
  28. return null;
  29. }
  30. //找到节点
  31. if(p.left!=null){
  32. return max(p.left);
  33. }
  34. //情况二:节点没有左子树(自左而来)
  35. return ancestorFromLeft!=null?ancestorFromLeft.value:null;
  36. }

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

  1. /**
  2. * <h3>查找最大关键字对应值</h3>
  3. * @return:关键字对应值
  4. */
  5. public Object max(){
  6. return max(root);
  7. }
  8. //写的更通用 方便找前驱等使用
  9. private Object max(BSTNode node){
  10. if(node==null){
  11. return null;
  12. }
  13. BSTNode p = node;
  14. while(p.right!=null){
  15. p = p.right;
  16. }
  17. return p.value;
  18. }

后继

  1. /**
  2. * <h3>查找关键字的后继值</h3>
  3. * @param:key-关键字
  4. * @return:后继值
  5. */
  6. public Object predecessor(int key){
  7. BSTNode p = root;
  8. BSTNode ancestorFromRight = null;
  9. while(p!=null){
  10. if(p.key < key){
  11. p = p.right;
  12. }else if(p.key > key){
  13. ancestorFromRight = p;
  14. p = p.left;
  15. }else{
  16. break;
  17. }
  18. }
  19. //没找到节点的情况
  20. if(p==null){
  21. return null;
  22. }
  23. //找到节点
  24. if(p.right!=null){
  25. return min(p.right);
  26. }
  27. //情况二:节点没有右子树(自右而来)
  28. return ancestorFromRight!=null?ancestorFromRight.value:null;
  29. }
  1. public Object min(){
  2. return min(root);
  3. }
  4. private Object min(BSTNode node){
  5. if(node==null){
  6. return null;
  7. }
  8. BSTNode P = node;
  9. while(P.left!=null){
  10. P = P.left;
  11. }
  12. return P.value;
  13. }

删除delete

第一种情况:没有左孩子

 情况二:没有右孩子

  1. /**
  2. * <h3>根据关键字删除</h3>
  3. * @param:key-关键字
  4. * @return:被删除关键字对应值
  5. */
  6. public Object delete(int key){
  7. //先找到节点
  8. BSTNode parent =null;//记录待删除节点的父亲
  9. BSTNode p = root;
  10. while(p!=null){
  11. if(key<p.key){
  12. parent = p;
  13. p = p.left;
  14. }else if(p.key < key){
  15. parent = p;
  16. p = p.right;
  17. }else{
  18. break;
  19. }
  20. }
  21. if(p==null){
  22. return null;
  23. }
  24. //找到了进行删除操作
  25. if(p.left==null && p.right!=null){
  26. //情况一:
  27. shift(parent,p,p.right);
  28. }else if(p.right==null&&p.left!=null){
  29. //情况二:
  30. shift(parent,p,p.left);
  31. }
  32. return p.value;
  33. }
  34. /**
  35. * 托孤方法
  36. * @param parent-被删除节点的父亲
  37. * @param deleted-被删除节点
  38. * @param child-被顶上去的节点
  39. */
  40. private void shift(BSTNode parent,BSTNode deleted,BSTNode child){
  41. if(parent==null){
  42. root = child;
  43. }else if(deleted==parent.left){
  44. parent.left = child;
  45. }else{
  46. parent.right =child;
  47. }
  48. }

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

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

  1. public Object delete(int key){
  2. //先找到节点
  3. BSTNode parent =null;//记录待删除节点的父亲
  4. BSTNode p = root;
  5. while(p!=null){
  6. if(key<p.key){
  7. parent = p;
  8. p = p.left;
  9. }else if(p.key < key){
  10. parent = p;
  11. p = p.right;
  12. }else{
  13. break;
  14. }
  15. }
  16. if(p==null){
  17. return null;
  18. }
  19. //找到了进行删除操作
  20. if(p.left==null){
  21. //情况一:
  22. shift(parent,p,p.right);
  23. }else if(p.right==null){
  24. //情况二:
  25. shift(parent,p,p.left);
  26. }else{//情况四
  27. //4.1被删除节点的后继节点
  28. BSTNode s = p.right;
  29. BSTNode sParent=p;//后继父亲
  30. while(s.left!=null){
  31. sParent = s;
  32. s= s.left;
  33. }
  34. //后继节点即为s
  35. if(sParent!=p){
  36. //说明不相邻
  37. 4.2删除节点和后继节点不相邻,要处理后继的后事
  38. shift(sParent,s,s.right);//不可能有左孩子 因为是后继
  39. s.right = p.right;
  40. }
  41. //4.3后继取代被删除节点
  42. shift(parent,p,s);
  43. s.left = p.left;
  44. }
  45. return p.value;
  46. }

整体代码:

  1. /**
  2. * Binary Search Tree 二叉搜索树
  3. */
  4. public class BSTTree1 {
  5. BSTNode root;//根节点
  6. static class BSTNode {
  7. int key;
  8. Object value;
  9. BSTNode left;
  10. BSTNode right;
  11. public BSTNode(int key){
  12. this.key = key;
  13. }
  14. public BSTNode(int key, Object value) {
  15. this.key = key;
  16. this.value = value;
  17. }
  18. public BSTNode(int key, Object value, BSTNode left, BSTNode right){
  19. this.key = key;
  20. this.value = value;
  21. this.left = left;
  22. this.right = right;
  23. }
  24. }
  25. /**
  26. * <h3>查找关键字对应的值</h3>
  27. * @param:key-关键字
  28. * @return:关键字对应的值
  29. */
  30. //非递归版本
  31. public Object get(int key){
  32. BSTNode node =root;
  33. while(node!=null){
  34. if(key<node.key){
  35. node = node.left;
  36. }else if(key>node.key){
  37. node = node.right;
  38. }else{
  39. return node.value;
  40. }
  41. }
  42. return null;
  43. }
  44. /**
  45. * <h3>查找最小关键字对应值</h3>
  46. * @Returns:关键字对应的值
  47. */
  48. // public Object min(){
  49. // return doMin(root);
  50. // }
  51. public Object min(){
  52. return min(root);
  53. }
  54. private Object min(BSTNode node){
  55. if(node==null){
  56. return null;
  57. }
  58. BSTNode P = node;
  59. while(P.left!=null){
  60. P = P.left;
  61. }
  62. return P.value;
  63. }
  64. public Object doMin(BSTNode node){
  65. if(node==null){
  66. return null;
  67. }
  68. if(node.left == null){//找到了最小的节点
  69. return node.value;
  70. }
  71. return doMin(node.left);//尾递归
  72. }
  73. /**
  74. * <h3>查找最大关键字对应值</h3>
  75. * @return:关键字对应值
  76. */
  77. public Object max(){
  78. return max(root);
  79. }
  80. //写的更通用 方便找前驱等使用
  81. private Object max(BSTNode node){
  82. if(node==null){
  83. return null;
  84. }
  85. BSTNode p = node;
  86. while(p.right!=null){
  87. p = p.right;
  88. }
  89. return p.value;
  90. }
  91. /**
  92. * <h3>存储关键字和对应值</h3>
  93. * @param:key 关键字
  94. * @param:value 值
  95. */
  96. public void put(int key,Object value){
  97. //1.key 有 更新
  98. //2.key 没有 更新
  99. BSTNode node = root;
  100. BSTNode parent =null;
  101. while(node!=null){
  102. parent = node;
  103. if(key <node.key){
  104. node = node.left;
  105. }else if(node.key<key){
  106. node=node.right;
  107. }else{
  108. //1
  109. node.value = value;
  110. return;
  111. }
  112. }
  113. if(parent == null){//树为空
  114. root = new BSTNode(key,value);
  115. return;
  116. }
  117. //2
  118. // new BSTNode(key,value);
  119. if(key< parent.key){
  120. parent.left = new BSTNode(key,value);
  121. }else if(key> parent.key){//也可以直接改为else即可
  122. parent.right = new BSTNode(key,value);
  123. }
  124. }
  125. /**
  126. * <h3>查找关键字的前驱值</h3>
  127. * @param:key-关键字
  128. * @return:前驱值
  129. */
  130. //中序遍历结果就是升序的结果
  131. //但是我们实际去实现的时候并不会采用中序遍历 因为性能不高 最坏的情况两个指针要把整个树走一次
  132. /*
  133. 前人的经验:
  134. 分为有左子树时,此时前驱节点就是左子树的最大值,图中属于这种情况的有
  135. 没有左子树时,若离它最近的祖先从左而来,此时祖先为前驱
  136. */
  137. public Object successor(int key) {
  138. BSTNode p = root;
  139. BSTNode ancestorFromLeft = null;
  140. while(p!=null){
  141. if(p.key < key){
  142. ancestorFromLeft = p;
  143. p = p.right;
  144. }else if(p.key > key){
  145. p = p.left;
  146. }else{
  147. break;
  148. }
  149. }
  150. //没找到节点的情况
  151. if(p==null){
  152. return null;
  153. }
  154. //找到节点
  155. if(p.left!=null){
  156. return max(p.left);
  157. }
  158. //情况二:节点没有左子树(自左而来)
  159. return ancestorFromLeft!=null?ancestorFromLeft.value:null;
  160. }
  161. /**
  162. * <h3>查找关键字的后继值</h3>
  163. * @param:key-关键字
  164. * @return:后继值
  165. */
  166. public Object predecessor(int key){
  167. BSTNode p = root;
  168. BSTNode ancestorFromRight = null;
  169. while(p!=null){
  170. if(p.key < key){
  171. p = p.right;
  172. }else if(p.key > key){
  173. ancestorFromRight = p;
  174. p = p.left;
  175. }else{
  176. break;
  177. }
  178. }
  179. //没找到节点的情况
  180. if(p==null){
  181. return null;
  182. }
  183. //找到节点
  184. if(p.right!=null){
  185. return min(p.right);
  186. }
  187. //情况二:节点没有右子树(自右而来)
  188. return ancestorFromRight!=null?ancestorFromRight.value:null;
  189. }
  190. /**
  191. * <h3>根据关键字删除</h3>
  192. * @param:key-关键字
  193. * @return:被删除关键字对应值
  194. */
  195. public Object delete(int key){
  196. //先找到节点
  197. BSTNode parent =null;//记录待删除节点的父亲
  198. BSTNode p = root;
  199. while(p!=null){
  200. if(key<p.key){
  201. parent = p;
  202. p = p.left;
  203. }else if(p.key < key){
  204. parent = p;
  205. p = p.right;
  206. }else{
  207. break;
  208. }
  209. }
  210. if(p==null){
  211. return null;
  212. }
  213. //找到了进行删除操作
  214. if(p.left==null){
  215. //情况一:
  216. shift(parent,p,p.right);
  217. }else if(p.right==null){
  218. //情况二:
  219. shift(parent,p,p.left);
  220. }else{//情况四
  221. //4.1被删除节点的后继节点
  222. BSTNode s = p.right;
  223. BSTNode sParent=p;//后继父亲
  224. while(s.left!=null){
  225. sParent = s;
  226. s= s.left;
  227. }
  228. //后继节点即为s
  229. if(sParent!=p){
  230. //说明不相邻
  231. 4.2删除节点和后继节点不相邻,要处理后继的后事
  232. shift(sParent,s,s.right);//不可能有左孩子 因为是后继
  233. s.right = p.right;
  234. }
  235. //4.3后继取代被删除节点
  236. shift(parent,p,s);
  237. s.left = p.left;
  238. }
  239. return p.value;
  240. }
  241. /**
  242. * 托孤方法
  243. * @param parent-被删除节点的父亲
  244. * @param deleted-被删除节点
  245. * @param child-被顶上去的节点
  246. */
  247. private void shift(BSTNode parent,BSTNode deleted,BSTNode child){
  248. if(parent==null){
  249. root = child;
  250. }else if(deleted==parent.left){
  251. parent.left = child;
  252. }else{
  253. parent.right =child;
  254. }
  255. }
  256. }

递归实现delete

    

  1. /**
  2. * <h3>根据关键字删除</h3>
  3. *
  4. * @param:key-关键字
  5. * @return:被删除关键字对应值
  6. */
  7. public Object delete(int key) {
  8. ArrayList<Object>result = new ArrayList<>();//保存被删除节点的值
  9. root = doDelete(root,key,result);
  10. return result.isEmpty()?null:result.get(0);
  11. }
  12. /*
  13. 递归版
  14. 1.删除节点没有左孩子,将右孩子托孤给Parent
  15. 2.删除节点没有右孩子,将左孩子托孤给Parent
  16. 3.删除节点左右孩子都没有,已经被涵盖在情况一,情况二当中,把null托孤给Parent
  17. 4.删除左右节点孩子都有,可以把它的后继节点(称之为S)托孤给parent
  18. 4
  19. / \
  20. 2 6
  21. / \
  22. 1 7
  23. node : 起点
  24. 返回值: 删剩下的孩子节点 或 null
  25. */
  26. private BSTNode doDelete(BSTNode node, int key,ArrayList<Object>result) {
  27. if (node == null) {
  28. return null;
  29. }
  30. if (key < node.key) {
  31. node.left = doDelete(node.left, key,result);//假如要删除2 那么返回1
  32. return node;
  33. }
  34. if (node.key < key) {
  35. node.right = doDelete(node.right, key,result);
  36. return node;
  37. }
  38. //找到了
  39. result.add(node.value);
  40. //情况1: 只有右孩子
  41. if (node.left == null) {
  42. return node.right;//返回的就是删剩下的东西
  43. }
  44. //情况2: 只有左孩子
  45. if (node.right == null) {
  46. return node.left;//返回的就是删剩下的东西
  47. }
  48. //情况3: 两个孩子都有
  49. BSTNode s = node.right;///后继节点
  50. while (s.left != null) {
  51. s = s.left;
  52. }
  53. s.right = doDelete(node.right, s.key,new ArrayList<>());//反正这个new的用不上 因为是删除后继节点的
  54. s.left = node.left;
  55. return s;
  56. }

范围查询

  1. /*
  2. 范围查询
  3. */
  4. //中序遍历-->升序
  5. /*
  6. 4
  7. / \
  8. 2 6
  9. / \ /\
  10. 1 3 5 7
  11. */
  12. //找 < key 的所有value
  13. public List<Object>less(int key){ //key = 6
  14. ArrayList<Object>result = new ArrayList<>();
  15. BSTNode p = root;
  16. LinkedList<BSTNode> stack = new LinkedList<>();
  17. while(p!=null||!stack.isEmpty()){
  18. if(p!=null){
  19. stack.push(p);
  20. p=p.left;
  21. }else{
  22. BSTNode pop = stack.pop();
  23. //处理值
  24. if(pop.key<key){
  25. result.add(pop.value);
  26. }else{
  27. break;
  28. }
  29. p = pop.right;
  30. }
  31. }
  32. return result;
  33. }
  34. //找>key的所有value
  35. /*
  36. Pre-order,NLR
  37. In-order,LNR
  38. Post-order,LRN
  39. Reverse Pre-order,NRL
  40. Reverse In-order,RNL
  41. Reverse post-order,RLN
  42. */
  43. public List<Object>greater(int key){ //优化 从后开始 反向中序遍历
  44. // ArrayList<Object>result = new ArrayList<>();
  45. // BSTNode p = root;
  46. // LinkedList<BSTNode> stack = new LinkedList<>();
  47. // while(p!=null||!stack.isEmpty()){
  48. // if(p!=null){
  49. // stack.push(p);
  50. // p=p.left;
  51. // }else{
  52. // BSTNode pop = stack.pop();
  53. // //处理值
  54. // if (key<pop.key){
  55. // result.add(pop.value);
  56. // }
  57. // //不能提前结束 因为ta要把全部走一次
  58. // p = pop.right;
  59. //
  60. //
  61. // }
  62. // }
  63. // return result;
  64. ArrayList<Object>result = new ArrayList<>();
  65. BSTNode p = root;
  66. LinkedList<BSTNode> stack = new LinkedList<>();
  67. while(p!=null||!stack.isEmpty()){
  68. if(p!=null){
  69. stack.push(p);
  70. p=p.right;
  71. }else{
  72. BSTNode pop = stack.pop();
  73. //处理值
  74. if (key<pop.key){
  75. result.add(pop.value);
  76. }
  77. else{
  78. break;
  79. }
  80. // System.out.println(pop.value);//逆序
  81. p = pop.left;
  82. }
  83. }
  84. return result;
  85. }
  86. //找>=key1||<=key2的所有值
  87. public List<Object>between(int key1,int key2){
  88. ArrayList<Object>result = new ArrayList<>();
  89. BSTNode p = root;
  90. LinkedList<BSTNode> stack = new LinkedList<>();
  91. while(p!=null||!stack.isEmpty()){
  92. if(p!=null){
  93. stack.push(p);
  94. p=p.left;
  95. }else{
  96. BSTNode pop = stack.pop();
  97. //处理值
  98. if(pop.key>=key1 && pop.key<=key2){
  99. result.add(pop.value);
  100. }else if(pop.key>key2){
  101. break;
  102. }
  103. p = pop.right;
  104. }
  105. }
  106. return result;
  107. }

二叉搜索树练习

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

删除节点-力扣450题

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

  • 当然要注意的是:题目中的树节点TreeNode相当于例题中的BSTNode
  • val,left,right(val即作为键又作为值)
  • key,value,left,right
  • Ta的TreeNode没有key,比较用的是TreeNode.val属性与待删除key进行比较
  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public TreeNode deleteNode(TreeNode node, int key) {
  18. if(node==null){
  19. return null;
  20. }
  21. if(key<node.val){
  22. node.left = deleteNode(node.left,key);
  23. return node;
  24. }
  25. if(node.val<key){
  26. node.right = deleteNode(node.right,key);
  27. return node;
  28. }
  29. if(node.left==null){//情况一:只有右孩子
  30. return node.right;
  31. }
  32. if(node.right == null){//情况二:只有左孩子
  33. return node.left;
  34. }
  35. TreeNode s = node.right;//情况三:有两个孩子
  36. while(s.left != null){
  37. s= s.left;
  38. }
  39. s.right = deleteNode(node.right,s.val);
  40. s.left = node.left;
  41. return s;
  42. }
  43. }

也可以这么写:

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public TreeNode deleteNode(TreeNode root, int key) {
  18. if(root == null){
  19. return null;
  20. }
  21. if(key > root.val){
  22. root.right = deleteNode(root.right,key);//去右子树删除
  23. }
  24. else if(key<root.val){
  25. root.left = deleteNode(root.left,key);//去左子树删除
  26. }
  27. else{//当前节点就是要删除的节点
  28. if(root.left==null){
  29. return root.right;//情况一
  30. }
  31. if(root.right==null){
  32. return root.left;//情况二
  33. }
  34. TreeNode node = root.right;//情况三
  35. while(node.left!=null){//寻找要删除节点右子树的最左节点
  36. node = node.left;
  37. }
  38. node.left = root.left;
  39. //将要删除节点的左子树成为其右子树的最左节点的左子树
  40. root = root.right;//将要删除节点的右子顶替它的位置,节点被删除
  41. }
  42. return root;
  43. }
  44. }

新增节点-力扣701

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

  1. /**
  2. * 新增节点(题目前提:val一定与树中节点不同)
  3. */
  4. public class Leetcode701 {
  5. /*
  6. put(key,value)
  7. - key 存在 更新
  8. - key 不存在 新增
  9. */
  10. /*
  11. val=7
  12. 5
  13. / \
  14. 2 6
  15. / \ \
  16. 1 3 7
  17. */
  18. public TreeNode insertIntoBST(TreeNode node,int val){
  19. if(node==null){//找到空位
  20. return new TreeNode(val);
  21. }
  22. if(val<node.val){
  23. //2.left = 2 多余的赋值动作
  24. node.left = insertIntoBST(node.left,val);//val=1 建立父子关系
  25. }else if(node.val<val){
  26. node.right = insertIntoBST(node.right,val);
  27. }
  28. return node;
  29. }
  30. }

查询节点-力扣700

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

  1. /**
  2. * Definition for a binary tree node.
  3. * public class TreeNode {
  4. * int val;
  5. * TreeNode left;
  6. * TreeNode right;
  7. * TreeNode() {}
  8. * TreeNode(int val) { this.val = val; }
  9. * TreeNode(int val, TreeNode left, TreeNode right) {
  10. * this.val = val;
  11. * this.left = left;
  12. * this.right = right;
  13. * }
  14. * }
  15. */
  16. class Solution {
  17. public TreeNode searchBST(TreeNode root, int val) {
  18. if(root==null){
  19. return null;
  20. }
  21. if(val<root.val){
  22. return searchBST(root.left,val);
  23. }else if(root.val<val){
  24. return searchBST(root.right,val);
  25. }else{
  26. return root;
  27. }
  28. }
  29. }

验证二叉搜索树-力扣98

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

  1. // 解法1:中序遍历非递归实现
  2. public boolean isValidBST(TreeNode node){
  3. TreeNode p = node;
  4. LinkedList<TreeNode> stack = new LinkedList<>();
  5. long prev=Long.MIN_VALUE;//代表上一个值
  6. while(p!=null || !stack.isEmpty()){
  7. if(p!=null){
  8. stack.push(p);
  9. p= p.left;
  10. }else{
  11. TreeNode pop = stack.pop();
  12. //处理值
  13. if(prev >= pop.val){
  14. return false;
  15. }
  16. prev = pop.val;
  17. p = pop.right;
  18. }
  19. }
  20. return true;
  21. }
  1. long pre = Long.MIN_VALUE;
  2. // 解法2:中序遍历递归实现
  3. public boolean isValidBST(TreeNode node){
  4. if(node==null){
  5. return true;
  6. }
  7. boolean a = isValidBST(node.left);
  8. //值
  9. if(pre >= node.val){
  10. return false;
  11. }
  12. pre = node.val;
  13. boolean b = isValidBST(node.right);
  14. return a&&b;
  15. }

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

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

 

 

  1. long pre = Long.MIN_VALUE;
  2. // 解法2:中序遍历递归实现
  3. public boolean isValidBST(TreeNode node){
  4. if(node==null){
  5. return true;
  6. }
  7. boolean a = isValidBST(node.left);
  8. if(!a){
  9. return false;//做一个提前的返回
  10. }
  11. //值
  12. if(pre >= node.val){
  13. return false;
  14. }
  15. pre = node.val;
  16. return isValidBST(node.right);;
  17. }

剪枝操作.

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

  1. public boolean isValidBST(TreeNode node){
  2. return doValid(node,Long.MIN_VALUE);
  3. }
  4. private boolean doValid(TreeNode node,long prev){
  5. if(node==null){
  6. return true;
  7. }
  8. boolean a = doValid(node.left,prev);
  9. if(!a){
  10. return false;
  11. }
  12. if(prev>=node.val){
  13. return false;
  14. }
  15. prev = node.val;
  16. return doValid(node.right,prev);
  17. /*
  18. 3
  19. \
  20. 4
  21. /
  22. 5
  23. */
  24. }

这样写看似没有问题 但在运行像上面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也就是作为是一个对象

  1. public boolean isValidBST(TreeNode node){
  2. return doValid(node,new AtomicLong(Long.MIN_VALUE));
  3. //始终传递的都是同一个对象 所以修改可以改变其他方法中prev
  4. //那能不能用Long 这个长整型作为对象呢?不行,Long和其他java
  5. //里的包装类型都属于不可变数据类型,里面的值是不能修改的
  6. //AtomicLong是可变数据类型,里面的数据是可以用set修改
  7. }
  8. private boolean doValid(TreeNode node, AtomicLong prev){
  9. if(node==null){
  10. return true;
  11. }
  12. boolean a = doValid(node.left,prev);
  13. if(!a){
  14. return false;
  15. }
  16. if(prev.get()>=node.val){
  17. return false;
  18. }
  19. prev.set(node.val);
  20. return doValid(node.right,prev);
  21. }

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

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

  1. /*
  2. 能否只判断父亲比左孩子大,比右孩子小?
  3. 4
  4. / \
  5. 2 6
  6. / \
  7. 1 3
  8. 反例:
  9. 4
  10. / \
  11. 2 6
  12. / \
  13. 3 7
  14. 只考虑了父子之间没有考虑祖先跟后代的大小关系,判断上就出现了疏漏
  15. */

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

  1. /*
  2. -∞ 4 +∞
  3. / \
  4. -∞ 2 4 4 6 +∞
  5. / \
  6. 4 3 6 6 7 +∞
  7. */
  8. //解法4:上下限递归实现 0ms
  9. public boolean isValidBST4(TreeNode node){
  10. return doValid4(node,Long.MIN_VALUE,Long.MAX_VALUE);
  11. }
  12. private boolean doValid4(TreeNode node,long min,long max){
  13. if(node==null){
  14. return true;
  15. }
  16. if(node.val<=min || node.val>=max){
  17. return false;
  18. }
  19. return doValid4(node.left,min,node.val) && doValid4(node.right,node.val,max);
  20. }

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

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

  1. // 解法1: 中序非递归实现 4ms
  2. public int rangeSumBST(TreeNode node,int low,int high){
  3. TreeNode p = node;
  4. LinkedList<TreeNode>stack = new LinkedList<>();
  5. int sum = 0;
  6. while(p!=null||!stack.isEmpty()){
  7. if(p!=null){
  8. stack.push(p);
  9. p=p.left;
  10. }else{
  11. TreeNode pop = stack.pop();
  12. //处理值
  13. if(pop.val>high){
  14. break;
  15. }//剪枝
  16. if(pop.val >= low){
  17. sum+=pop.val;
  18. }
  19. p = pop.right;
  20. }
  21. }
  22. return sum;
  23. }

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

  1. //中序遍历性能不高的原因在于不能够有效的筛选 对high可以剪枝 但是对low没有办法有效的剪枝
  2. // 解法2:上下限递归
  3. /*
  4. 10
  5. / \
  6. 5 15
  7. / \ \
  8. 3 7 18 low=7 high=15
  9. */
  10. public int rangeSumBST(TreeNode node,int low,int high){
  11. if(node==null){
  12. return 0;
  13. }
  14. if(node.val<low){
  15. return rangeSumBST(node.right,low,high);
  16. }
  17. if(node.val>high){
  18. return rangeSumBST(node.left,low,high);
  19. }
  20. // 在范围内
  21. return node.val + rangeSumBST(node.left, low, high) + rangeSumBST(node.right, low, high);
  22. }

 

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

  1. /**
  2. * 根据前序遍历构造二叉搜索树 题目说明 preorder 长度>=1
  3. */
  4. public class Leetcode1008 {
  5. /*
  6. 1.preorder长度>=1
  7. 2.preorder没有重复值
  8. 8, 5, 1, 7, 10, 12
  9. 8
  10. / \
  11. 5 10
  12. / \ \
  13. 1 7 12
  14. */
  15. public TreeNode bstFromPreorder(int[] preorder){
  16. TreeNode root = new TreeNode(preorder[0]);
  17. for (int i = 1; i < preorder.length; i++) {
  18. int val = preorder[i];
  19. insert(root,val);//n * log(n)
  20. }
  21. return root;
  22. }
  23. private TreeNode insert(TreeNode node,int val){
  24. if(node==null){
  25. return new TreeNode(val);
  26. }
  27. if(val<node.val){
  28. node.left = insert(node.left,val);
  29. }else if(node.val<val){
  30. node.right = insert(node.right,val);
  31. }//没有重复值 直接不写else
  32. return node;
  33. }
  34. }

方法二: 

  1. // 上限法 8 5 1 7 10 12
  2. /*
  3. 1.遍历数组中的每一个值,根据值创建节点
  4. - 每个人节点若成功创建:做孩子上限,右孩子上限
  5. 2.处理下一个值时,如果超过此上限,那么
  6. - 将null作为上个节点的孩子
  7. - 不能创建节点对象
  8. - 直到不超过上限为止
  9. 3.重复1,2步骤
  10. */
  11. public TreeNode bstFromPreorder(int[] preorder){
  12. return insert(preorder,Integer.MAX_VALUE);
  13. }
  14. /*
  15. 依次处理 preorder 中每个值,返回创建好的节点或null
  16. 1.如果超过上限,返回null,作为孩子返回
  17. 2.如果没超过上限,创建节点,并设置其左右孩子
  18. 左右孩子完整后返回
  19. */
  20. int i = 0;//索引从0开始
  21. private TreeNode insert(int[] preorder,int max){
  22. if(i==preorder.length){
  23. return null;
  24. }
  25. int val = preorder[i];
  26. if(val>max){
  27. return null;
  28. }
  29. //没有超过上限
  30. TreeNode node = new TreeNode(val);
  31. i++;//找到下一个值看看下一个值能不能作为左孩子或者右孩子
  32. node.left = insert(preorder,val);//左孩子上限就是自身的值
  33. node.right= insert(preorder,max);//
  34. return node;
  35. }

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

分治法:O(n*logn)

  1. //分治法
  2. /*
  3. 8 5 1 7 10 12
  4. 根 8
  5. 左 5 1 7
  6. 右 10 12
  7. 根 5 左 1 右 7
  8. 根 10 左 null 右 12
  9. */
  10. public TreeNode bstFromPreorder1(int[] preorder){
  11. return partition(preorder,0,preorder.length);
  12. }
  13. private TreeNode partition(int[] preorder,int start,int end){
  14. if(start>end){
  15. return null;
  16. }
  17. TreeNode root = new TreeNode(preorder[start]);
  18. int index = start+1;
  19. while(index<=end){
  20. if(preorder[index]>preorder[start]){
  21. break;
  22. }
  23. index++;
  24. }
  25. //index是右子树的起点
  26. root.left = partition(preorder,start+1,index-1);
  27. root.right = partition(preorder,index,end);
  28. return root;
  29. }

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

  1. /**
  2. * 求二叉搜索树最近公共祖先(祖先也包括自己)
  3. * 前提:
  4. * <ul>
  5. * <li>节点值唯一</li>
  6. * <li>p和q都存在</li>
  7. * </ul>
  8. */
  9. public class Leetcode235 {
  10. /*
  11. _ _ 6_ _
  12. / \
  13. 2 8
  14. / \ / \
  15. 0 4 7 9
  16. / \
  17. 3 5
  18. 待查找节点p q 在某一节点的两侧,那么此节点就是最近的公共祖先
  19. */
  20. public TreeNode lowestCommonAncestor(TreeNode root,TreeNode p,TreeNode q){
  21. TreeNode a = root;
  22. while(p.val<a.val&&q.val<a.val || a.val<p.val && a.val<q.val){ //在同一侧
  23. if(p.val<a.val){
  24. a = a.left;
  25. }else{
  26. a= a.right;
  27. }
  28. }
  29. return a;
  30. }
  31. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/499404
推荐阅读
相关标签
  

闽ICP备14008679号