【Java】二叉搜索树(BST)

二叉搜索树

二叉搜索树的概念

二叉树

手搓二叉搜索树

MyBinarySearchTree的成员变量和构造函数

java 复制代码
public TreeNode root; // 树的根节点

// 静态内部类:节点结构
static class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;

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

MyBinarySearchTree的辅助函数

辅助类:包装节点及其父节点

java 复制代码
// 辅助类:包装节点及其父节点,方便返回多个值
 public class NodePair {
     public TreeNode node1;  // 目标节点
     public TreeNode node2;  // 父节点

     public NodePair(TreeNode node1, TreeNode node2) {
         this.node1 = node1;
         this.node2 = node2;
     }
 }

辅助方法:在给定节点的右子树中寻找最小值(最左节点)

java 复制代码
 // 辅助方法:在给定节点的右子树中寻找最小值(最左节点)
 private NodePair findLeftmostNode(TreeNode cur) {
     TreeNode leftmostNode = cur.right;
     TreeNode leftmostNodeParent = cur;

     if (leftmostNode == null) return new NodePair(null, cur);

     while (leftmostNode.left != null) {
         leftmostNodeParent = leftmostNode;
         leftmostNode = leftmostNode.left;
     }
     return new NodePair(leftmostNode, leftmostNodeParent);
 }

辅助方法:在给定节点的左子树中寻找最大值(最右节点)

java 复制代码
// 辅助方法:在给定节点的左子树中寻找最大值(最右节点)
private NodePair findRightmostNode(TreeNode cur) {
    TreeNode rightmostNode = cur.left;
    TreeNode rightmostNodeParent = cur;

    if (rightmostNode == null) return new NodePair(null, cur);

    while (rightmostNode.right != null) {
        rightmostNodeParent = rightmostNode;
        rightmostNode = rightmostNode.right;
    }
    return new NodePair(rightmostNode, rightmostNodeParent);
}

辅助方法:交换两个节点的值

java 复制代码
// 交换两个节点的值
private void swapNodeVal(TreeNode node1, TreeNode node2) {
    int tmp = node1.val;
    node1.val = node2.val;
    node2.val = tmp;
}

MyBinarySearchTree查找元素:search( )

java 复制代码
/**
* 查找方法
* @param key 要查找的值
* @return 找到的节点,若没找到则返回 null
*/
public TreeNode search(int key) {
   TreeNode cur = root;
   while (cur != null) {
       if (cur.val == key) {
           return cur; // 找到目标值
       } else if (cur.val < key) {
           cur = cur.right; // 目标值大,向右走
       } else {
           cur = cur.left;  // 目标值小,向左走
       }
   }
   return null;
}

MyBinarySearchTree插入元素:insert ()

java 复制代码
/**
* 插入方法
* @param key 要插入的值
*/
public void insert(int key) {
   // 情况 1:树为空,直接作为根节点
   if (root == null) {
       root = new TreeNode(key);
       return;
   }

   // 情况 2:树不为空,寻找插入位置
   TreeNode cur = root;
   TreeNode curParent = null; // 需要记录父节点,否则找到位置后无法连接
   
   while (cur != null) {
       if (cur.val == key) {
           // BST 通常不包含重复 key,若存在则直接返回
           return;
       } else if (cur.val < key) {
           curParent = cur;
           cur = cur.right;
       } else {
           curParent = cur;
           cur = cur.left;
       }
   }

   // 此时 cur 为空,curParent 是叶子节点
   TreeNode newNode = new TreeNode(key);
   if (curParent.val > key) {
       curParent.left = newNode; // 比父节点小,连在左边
   } else {
       curParent.right = newNode; // 比父节点大,连在右边
   }
}

MyBinarySearchTree删除指定元素:remove( )⭐

java 复制代码
/**
 * 删除方法
 * @param key 要删除的值
 */
public void remove(int key) {
    if (root == null) {
        System.out.println("树为空!");
        return;
    }

    // 1. 寻找要删除的节点及其父节点
    TreeNode cur = root;
    TreeNode parent = null;

    while (cur != null && cur.val != key) {
        parent = cur;
        if (key < cur.val) {
            cur = cur.left;
        } else {
            cur = cur.right;
        }
    }

    if (cur == null) {
        System.out.println("没有找到!");
        return;
    }

    // 2. 开始删除逻辑
    // 情况 A:删除叶子节点(左右都为空)
    if (cur.left == null && cur.right == null) {
        if (cur == root) {
            root = null; // 删的是根节点且它是唯一节点
        } else if (parent.left == cur) {
            parent.left = null;
        } else {
            parent.right = null;
        }
    }
    // 情况 B:删除只有左子树的节点
    else if (cur.right == null) {
        if (cur == root) {
            root = cur.left;
        } else if (parent.left == cur) {
            parent.left = cur.left; // 父节点接手孙子节点
        } else {
            parent.right = cur.left;
        }
    }
    // 情况 C:删除只有右子树的节点
    else if (cur.left == null) {
        if (cur == root) {
            root = cur.right;
        } else if (parent.left == cur) {
            parent.left = cur.right;
        } else {
            parent.right = cur.right;
        }
    }
    // 情况 D:删除有两个子树的节点(最复杂的情况)
    else {
        // 采用"替换法":找到右子树中的最小值(最左节点)来顶替当前位置
        NodePair pair = findLeftmostNode(cur);
        TreeNode leftmostNode = pair.node1;
        TreeNode leftmostNodeParent = pair.node2;

        // 将右子树最小节点的值覆盖到当前要删除的节点上
        swapNodeVal(cur, leftmostNode);

        // 现在问题转化为删除那个"最左节点"
        // 最左节点一定没有左孩子,但可能有右孩子
        if (leftmostNodeParent.left == leftmostNode) {
            leftmostNodeParent.left = leftmostNode.right;
        } else {
            // 特殊情况:cur.right 就是最左节点时(即 cur.right 没有左子树)
            leftmostNodeParent.right = leftmostNode.right;
        }
    }
}

完整测试

java 复制代码
public class TestBST {
    public static void main(String[] args) {
        MyBinarySearchTree bst = new MyBinarySearchTree();

        // 1. 测试插入
        int[] data = {50, 30, 70, 20, 40, 60, 80, 35};
        System.out.println("--- 插入测试 ---");
        for (int val : data) {
            bst.insert(val);
        }
        System.out.print("当前树(中序遍历,应有序): ");
        bst.inorderTraversal(); // 期待输出: 20 30 35 40 50 60 70 80

        // 2. 测试查找
        System.out.println("\n--- 查找测试 ---");
        int searchKey = 40;
        MyBinarySearchTree.TreeNode found = bst.search(searchKey);
        System.out.println("查找 " + searchKey + (found != null ? ": 成功" : ": 失败"));
        
        searchKey = 100;
        found = bst.search(searchKey);
        System.out.println("查找 " + searchKey + (found != null ? ": 成功" : ": 失败"));

        // 3. 测试删除:叶子节点 (20)
        System.out.println("\n--- 删除测试:叶子节点 (20) ---");
        bst.remove(20);
        bst.inorderTraversal(); // 期待: 30 35 40 50 60 70 80

        // 4. 测试删除:只有一个子节点的节点 (40)
        // 此时 40 的左边是 35
        System.out.println("\n--- 删除测试:单分支节点 (40) ---");
        bst.remove(40);
        bst.inorderTraversal(); // 期待: 30 35 50 60 70 80

        // 5. 测试删除:有两个子节点的节点 (70)
        // 70 的左边是 60,右边是 80
        System.out.println("\n--- 删除测试:双子树节点 (70) ---");
        bst.remove(70);
        bst.inorderTraversal(); // 期待: 30 35 50 60 80

        // 6. 测试删除:根节点 (50)
        System.out.println("\n--- 删除测试:根节点 (50) ---");
        bst.remove(50);
        bst.inorderTraversal(); // 期待: 30 35 60 80

        // 7. 测试删除不存在的节点
        System.out.println("\n--- 删除测试:不存在的节点 (99) ---");
        bst.remove(99);
    }
}
相关推荐
weixin_437957612 小时前
Mysql安装不成功
java
Lyyaoo.2 小时前
【JAVA基础面经】进程安全问题(synchronized and volatile)
java·开发语言·jvm
Andya_net2 小时前
Java | 基于 Feign 流式传输操作SFTP文件传输
java·开发语言·spring boot
赵优秀一一2 小时前
FastAPI 核心
linux·python·fastapi
无限进步_2 小时前
【C++】多重继承中的虚表布局分析:D类对象为何有两个虚表?
开发语言·c++·ide·windows·git·算法·visual studio
_Evan_Yao2 小时前
别让“规范”困住你:前后端交互中的方法选择与认知突围
java·后端·交互·restful
清水白石0082 小时前
向后兼容的工程伦理:Python 开发中“优雅重构”与“责任担当”的平衡之道
开发语言·python·重构
wgzrmlrm742 小时前
mysql如何配置全文索引停用词_mysql ft_stopword_file设置
jvm·数据库·python
A.A呐2 小时前
【QT第六章】界面优化
开发语言·qt