Java——二叉搜索树(二叉排序树)的查找、插入、删除

二叉搜索树

概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1、若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2、若它的右子树不为空,则右子树上所有节点的值都大于根节点的值它的左右子树也分别为二叉搜索树

二叉搜索树中序遍历的结果是一个有序数组

构建一个二叉树
在这里插入图片描述

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

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

查找

查找过程:
1、拿着待查元素和根节点比较;
2、如果比根节点小,继续在左子树中查找;
3、如果比根节点大,继续在右子树中查找;
4、如果当前元素查找到null也没找到,说明该元素不存在。

普通的二叉搜索树查找时间复杂度O(N),如果一个二叉搜索树比较平衡 (左右子树高度差<=1为平衡二叉树(AVL树),左右子树高度差越小,越平衡),那么查找效率比较高。
在这里插入图片描述
在这里插入图片描述

  //记录树的根节点,root==null,当前是个空树
    private Node root = null;

    public Node find(int key) {
        Node cur = root;
        while (cur != null){
            if(key < cur.key){
                //左子树中查找
                cur = cur.left;
            }else if(key > cur.key){
                //右子树中查找
                cur = cur.right;
            }else {
                return cur;
            }
        }
        return null;
    }

插入

1、先找到插入位置,先让插入的节点和根节点比较大小,跟之前的查找方式相似。
2、找到合适位置进行节点插入。

新元素被插入之后一定是个叶子节点

在这里插入图片描述
1、先定义一个节点指向根节点,prev初始为空,prev始终指向cur的父节点,查找过程中,当cur为null时,此时就可以认为找到了合适的差插入位置,此时prev指向的节点就是插入位置。

2、之后判断节点是插入到prev的左子树下还是右子树下,让需要插入的节点和prev节点的值进行比较,小于插入左子树,大于插入右子树。
在这里插入图片描述

public Node insert(int key, int value){
        //1、如果树本身是空树,直接让root指向新节点
        if(root == null){
            root = new Node(key, value);
            return root;
        }
        //2、先找到key所在的位置
        Node cur = root;
        Node prev = null;
        while (cur != null){
            if(cur.key > key){
                //继续往左找
                prev = cur;
                cur = cur.left;
            }else if(cur.key < key){
                //继续往右找
                prev = cur;
                cur = cur.right;
            }else {
                //找到了和key相同的元素
                //针对key相同
                // 1、直接插入失败
                // 2、不创建新节点,把当前的节点的value改成新的value(和标准库一致)
                cur.value = value;
                return cur;

            }
        }
        //循环结束,cur为null,直到了合适的插入地点
        //新节点判断大小之后插入到prev节点的下面
        Node newNode = new Node(key, value);
        if(newNode.key < prev.key){
            prev.left = newNode;
        }else {
            prev.right = newNode;
        }
        return newNode;
    }

删除

删除操作进行的时候,需要先找到要删除的元素,删除情况要分多种情况进行讨论。
讨论:
1、先按照cur的左右子树分别为空分成两大类;
2、在每个大类里面。看cur是父亲的左子树还是右子树
下面就罗列出了可能,画圈的的元素表示要删除元素。

(一)、 cur.left == null
1 ) cur ==root
思路:root = cur.right
在这里插入图片描述
2)cur != root,cur是parent的左子树 cur=parent.left
思路:parent.left = cur.right
在这里插入图片描述
3)cur != root,cur是parent.right
思路:parent.right = cur.right
在这里插入图片描述

========================================

(二)、 cur.right == null

1) cur == root
思路 :root == cur.left
在这里插入图片描述
2)cur != root cur是parent.left
思路:parent = cur.left
在这里插入图片描述
3)cur != root cur是parent.right
思路:parent.right = cur.left
在这里插入图片描述

========================================
(三)、 cur.left != null && cur.right != null
在这里插入图片描述
在这里插入图片描述

 private void removeNode(Node cur, Node parent) {
       //左右子树都无归类到1、2中都可
        if(cur.left == null){
            //1、没有左子树
            if(cur == root){
                //1.1如果cur为根节点
                    root = cur.right;
            }else if(cur == parent.left){
                //1。2cur不为根节点而且为parent的左子树
                parent.left = cur.right;
            }else if(cur == parent.right){
               // 1.3cur不为根节点,且为parent的右子树
                parent.right = cur.right;
            }
        }else if(cur.right == null){
            //2、没有右子树
            if(cur == root){
                //2.1如果cur为根节点
                root = cur.left;
            }else if(cur == parent.left){
                //2。2cur不为根节点而且为parent的左子树
                parent.left = cur.left;
            }else if(cur == parent.right){
                // 2.3cur不为根节点,且为parent的右子树
                parent.right = cur.left;
            }

        }else {
            //左右子树都有
            //1、先找到替罪羊节点,同时找到替罪羊的父节点
            Node scapeGoat = cur.right;
            Node scapeGoatParent = cur;
            while (scapeGoat.left != null){
                scapeGoatParent = scapeGoat;
                scapeGoat = scapeGoat.left;
            }
            //循环结束之后,scapeGoat就指向了右子树中的最小值(最左侧节点)
            //2、把替罪羊的节点的key和value设置给cur
            cur.key = scapeGoat.key;
            cur.value = scapeGoat.value;
            //3、删除替罪羊节点
            if(scapeGoat == scapeGoatParent.left){
                scapeGoatParent.left = scapeGoat.right;
            }else {
                scapeGoatParent.right = scapeGoat.right;
            }
        }

注:将查找方法改为boolean类型方便检测
操作:

public class BinarySearTree {
    class Node{
        public int key;
        public int value;
        Node left;
        Node right;

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

    //记录树的根节点,root==null,当前是个空树
    public Node root = null;

    public boolean find(int key) {
        Node cur = root;
        while (cur != null){
            if(key < cur.key){
                //左子树中查找
                cur = cur.left;
            }else if(key > cur.key){
                //右子树中查找
                cur = cur.right;
            }else {
                return true;
            }
        }
        return false;
    }

    public Node insert(int key, int value){
        //1、如果树本身是空树,直接让root指向新节点
        if(root == null){
            root = new Node(key, value);
            return root;
        }
        //2、先找到key所在的位置
        Node cur = root;
        Node prev = null;
        while (cur != null){
            if(cur.key > key){
                //继续往左找
                prev = cur;
                cur = cur.left;
            }else if(cur.key < key){
                //继续往右找
                prev = cur;
                cur = cur.right;
            }else {
                //找到了和key相同的元素
                //针对key相同
                // 1、直接插入失败
                // 2、不创建新节点,把当前的节点的value改成新的value(和标准库一致)
                cur.value = value;
                return cur;

            }
        }
        //循环结束,cur为null,直到了合适的插入地点
        //新节点判断大小之后插入到prev节点的下面
        Node newNode = new Node(key, value);
        if(newNode.key < prev.key){
            prev.left = newNode;
        }else {
            prev.right = newNode;
        }
        return newNode;
    }

    public void remove(int key){
        //1、首先找到要删除节点的位置,同时记录下父节点位置
        Node cur = root;
        Node parent = null;
        while (cur != null){
            if(cur.key > key){
                //继续往左找
                parent = cur;
                cur = cur.left;
            }else if(cur.key < key){
                //继续往右找
                parent = cur;
                cur = cur.right;
            }else {
                //找到了要删除的节点
                removeNode(cur,parent);
                return;

            }
    }
        //没有找到节点,删除失败
        return;
}

    private void removeNode(Node cur, Node parent) {
       //左右子树都无归类到1、2中都可
        if(cur.left == null){
            //1、没有左子树
            if(cur == root){
                //1.1如果cur为根节点
                    root = cur.right;
            }else if(cur == parent.left){
                //1。2cur不为根节点而且为parent的左子树
                parent.left = cur.right;
            }else if(cur == parent.right){
               // 1.3cur不为根节点,且为parent的右子树
                parent.right = cur.right;
            }
        }else if(cur.right == null){
            //2、没有右子树
            if(cur == root){
                //2.1如果cur为根节点
                root = cur.left;
            }else if(cur == parent.left){
                //2。2cur不为根节点而且为parent的左子树
                parent.left = cur.left;
            }else if(cur == parent.right){
                // 2.3cur不为根节点,且为parent的右子树
                parent.right = cur.left;
            }

        }else {
            //左右子树都有
            //1、先找到替罪羊节点,同时找到替罪羊的父节点
            Node scapeGoat = cur.right;
            Node scapeGoatParent = cur;
            while (scapeGoat.left != null){
                scapeGoatParent = scapeGoat;
                scapeGoat = scapeGoat.left;
            }
            //循环结束之后,scapeGoat就指向了右子树中的最小值(最左侧节点)
            //2、把替罪羊的节点的key和value设置给cur
            cur.key = scapeGoat.key;
            cur.value = scapeGoat.value;
            //3、删除替罪羊节点
            if(scapeGoat == scapeGoatParent.left){
                scapeGoatParent.left = scapeGoat.right;
            }else {
                scapeGoatParent.right = scapeGoat.right;
            }
        }
    }

}

测试:

public class Test {
    private static void preOrder(BinarySearTree.Node root){
        if(root == null){
            return;
        }
        System.out.print(root.key + " ");
        preOrder(root.left);
        preOrder(root.right);
    }
    private static void inOrder(BinarySearTree.Node root){
        if(root == null){
            return;
        }
        inOrder(root.left);
        System.out.print(root.key + " ");
        inOrder(root.right);
    }

    public static void main(String[] args) {
        BinarySearTree tree = new BinarySearTree();
        int[] array = {1,9,5,2,7,4,6,8};
        for (int i: array) {
            tree.insert(i,32);
        }
        preOrder(tree.root);
        System.out.println();
        inOrder(tree.root);
        System.out.println();
        System.out.println(tree.find(5));
        System.out.println(tree.find(778));
        preOrder(tree.root);
        System.out.println();
        inOrder(tree.root);
        tree.remove(5);
        System.out.println();
        inOrder(tree.root);
        tree.remove(9);
        System.out.println();
        inOrder(tree.root);
        tree.remove(8);
        System.out.println();
        preOrder(tree.root);
        System.out.println();
        inOrder(tree.root);

    }
}

1 9 5 2 4 7 6 8 
1 2 4 5 6 7 8 9 
true
false
1 9 5 2 4 7 6 8 
1 2 4 5 6 7 8 9 
1 2 4 6 7 8 9 
1 2 4 6 7 8 
1 6 2 4 7 
1 2 4 6 7 
Process finished with exit code 0
  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值