• [模版总结] - 树的基本算法2 - BST


    BST定义

    BST - Binary Search Tree, 即二叉搜索树(有序二叉树)

    特性

    • 中序遍历有序
    • 查找/插入/删除某个数值可以通过O(h) 即树的高度,最优logN,最坏 N.
      • 有多种改进BST可以动态维持插入删除后树结构能尽可能保持平衡

    BST vs. 有序数组 vs. 普通数组 - 引用自 古城算法 https://www.youtube.com/watch?v=DpkTu2tU87o&list=PLbaIOC0vpjNVHmklf0nzPlPsNvJiqq1lZ&index=3

    BST基本操作

    查询 - 二分查找

    • 搜索数值 - 二分法
    1. class Solution {
    2. public TreeNode searchBST(TreeNode root, int val) {
    3. while(root!=null) {
    4. if (root.val
    5. root = root.right;
    6. } else if (root.val>val) {
    7. root = root.left;
    8. } else {
    9. return root;
    10. }
    11. }
    12. return null;
    13. }
    14. }
    • 搜索临近数值
    1. class Solution {
    2. double min = Double.MAX_VALUE;
    3. int res = -1;
    4. public int closestValue(TreeNode root, double target) {
    5. dfs(root, target);
    6. return res;
    7. }
    8. private void dfs(TreeNode root, double target) {
    9. if (root==null) return;
    10. if (Math.abs(root.val - target) < min) {
    11. min = Math.abs(root.val - target);
    12. res = root.val;
    13. } else if (Math.abs(root.val - target) == min) {
    14. res = root.val
    15. }
    16. if (root.val>target) dfs(root.left, target);
    17. else dfs(root.right, target);
    18. }
    19. }

    插入

    插入则是首先找到需要插入的位置,然后插入新结点

    1. class Solution {
    2. public TreeNode insertIntoBST(TreeNode root, int val) {
    3. return dfs(root, val);
    4. }
    5. private TreeNode dfs(TreeNode root, int val) {
    6. if (root==null) {
    7. root = new TreeNode(val);
    8. return root;
    9. }
    10. if (root.val > val) root.left = dfs(root.left, val);
    11. else root.right = dfs(root.right, val);
    12. return root;
    13. }
    14. }

    删除

    删除操作较为复杂一点,我们在删除之后还需要维护当前二叉搜索树的性质,有三种情况:

    1. 删除叶子结点,不会影响BST性质,直接删除即可
    2. 删除结点没有右子树,也就是删除后左子树不会影响BST性质,将左子树root直接顶替删除结点的位置即可
    3. 删除结点有左右子树,为了保证BST性质,我们选择删除点的后继结点作为顶替结点,也就是删除结点右子树最左边的那个点,因为可以保证右子树所有点都大于该点,维持了BST性质
    1. class Solution {
    2. public TreeNode deleteNode(TreeNode root, int key) {
    3. /**
    4. 删除三种情况
    5. 1. 叶子结点
    6. 2. 只存在一个子树
    7. 3. 左右都存在子树
    8. */
    9. return dfs(root, key);
    10. }
    11. private TreeNode dfs(TreeNode root, int key) {
    12. if (root==null) return null;
    13. if (root.val==key) {
    14. if (root.left==null && root.right==null) return null;
    15. else if (root.left==null || root.right==null) {
    16. if (root.left!=null) return root.left;
    17. if (root.right!=null) return root.right;
    18. } else {
    19. // 找到root的后继结点,也就是右子树最左边的那个点
    20. TreeNode dum = root.right;
    21. while (dum.left!=null) {
    22. dum = dum.left;
    23. }
    24. root.val = dum.val;
    25. root.right = dfs(root.right, root.val);
    26. }
    27. } else if (root.val>key) {
    28. root.left = dfs(root.left, key);
    29. } else {
    30. root.right = dfs(root.right, key);
    31. }
    32. return root;
    33. }
    34. }

    前驱/后继结点

    Leetcode 285

    求解某一个点的前驱结点思路存储一个变量prev来保存进行下一层递归前的结点信息,如果中序遍历递归遍历到目标结点,其实保存的prev就是该结点的前驱结点

    1. private void preSuccessor(TreeNode root, TreeNode p) {
    2. if (node==null) return;
    3. preSuccessor(node.left, p);
    4. if (root==p) return prev;
    5. prev = root;
    6. preSuccessor(node.right, p);
    7. }

    求解后躯结点较为复杂,需要考虑到几种情况:

    1. 目标结点有右子树,那么后继结点则是右子树中leftmost结点
    2. 如果目标结点没有右子树,那么后继结点则可能是parent中的某个结点

    求解上述第二类后继结点思路类似,前驱结点是当前递归层处理的结点是目标结点时,prev保存的值即为前驱结点;后继结点可以理解为当前递归层的前驱结点时目标结点时,那么当前结点就是目标结点的后继结点,有点逆向思维哈哈。

     

    1. class Solution {
    2. // 需要前驱结点信息
    3. TreeNode prev;
    4. TreeNode insuccessor;
    5. public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
    6. if (p.right!=null) {
    7. TreeNode node = p.right;
    8. while (node.left!=null) {
    9. node = node.left;
    10. }
    11. return node;
    12. } else {
    13. helper(root, p);
    14. }
    15. return insuccessor;
    16. }
    17. private void helper(TreeNode node, TreeNode p) {
    18. if (node==null) return;
    19. helper(node.left, p);
    20. // check 当前结点
    21. if (prev!=null && prev==p) {
    22. insuccessor = node;
    23. }
    24. prev = node; //如果 当前结点前驱结点==p那么这个结点就是p的后驱结点
    25. helper(node.right, p);
    26. }
    27. }

    验证是否为BST

    Leetcode 98. Validate BST

    基本思路就是确保左结点 < 根结点 < 右结点,但是还需要保证局部正确的同时,左子树全部结点 < 根结点 < 右子树全部结点。所以每一次向下递归左子树时要以当前结点值作为上限值,遍历右子树时以当前结点值作为下限值

    1. class Solution {
    2. public boolean isValidBST(TreeNode root) {
    3. return helper(root, null, null);
    4. }
    5. private boolean helper(TreeNode root, Integer low, Integer high) {
    6. if (root==null) return true;
    7. if ((low!=null && root.val<=low) || (high!=null && root.val>=high)) {
    8. return false;
    9. }
    10. return helper(root.left, low, root.val) && helper(root.right, root.val, high);
    11. }
    12. }
  • 相关阅读:
    Verilog代码之求勾股定理和arctan
    hadoop fs,hadoop dfs以及hdfs dfs区别
    测试左移?测试右移?测试人员往哪移?
    阿里云serverless从入门到进阶测评(阿里云/AWS/腾讯云)
    Mybatis快速入门
    【MAC】MacOS M2 芯片的Mysql 数据库安装与使用
    网工内推 | 合资公司网工,CCNP/HCIP认证优先,朝九晚六
    【Unity】进度条和血条的三种做法
    C语言中的函数
    【cfeng-work】架构演进和漫谈
  • 原文地址:https://blog.csdn.net/ok1382038/article/details/134343947