二叉搜索树

二叉搜索树:笔试一般不会考,但是面试一般总考。
二叉搜索树:是一个特殊的二叉树,它要求左子树的所有节点都比根节点值小,要求右子树的所有节点都比根节点值大。
二叉搜索树的最核心用途:是用来查找元素的,类似于二分查找,中序遍历二叉搜索树的话,得到的是一个有序序列。
二叉搜索树的查找过程
根节点和要查找元素进行比较,根节点比待查找元素大,就去左子树中找;
根节点比待查找元素小,就去右子树中找;
依次再比较待查找元素和左右子树的根节点的值,再去递归的找左子树或右子树;
它避免了遍历整个树,比较高效。
二叉搜索树的时间复杂度:最坏情况:O(n),当前是一个通用的二叉搜索树,可能会出现极端情况导致查找效率较低,最好是O(logN)平衡二叉树的情况,比较平衡的情况。
解决方案是:采取更平衡的二叉搜索树:AVL树(绝对平衡) 红黑树(没那么平衡但是也说的过去,TreeSet和TreeMap就是基于红黑树实现的)
Map Set
TreeMap        ~~~~~~        TreeSet        ~~~~~~        基于二叉搜索树实现
HashMap        ~~~~~~        HashSet        ~~~~~~        基于哈希表
二叉搜索树的基本操作
1:插入元素:和查找非常相似,需要先找到待插入元素的合适位置,元素都是插入到叶子节点上(为了插入方便)
2:查找元素:也很简单
3:删除元素:需要考虑很多种不同情况:

  • 待删除元素是父节点的左子树,并且待删除元素左子树为空,右子树非空。方法:parent.left = cur.right

  • 待删除元素是父节点的右子树,待删除元素的左子树为空,右子树为非空。方法:parent.right =
    cur.right

  • 待删除元素是父节点的左子树,待删除元素左子树为非空,右子树为空。方法:parent.left = cur.left

  • 待删除元素是父节点的左子树,待删除元素左子树为非空,右子树为空。方法:parent.left = cur.left;

  • 待删除是父节点的左子树,待删除元素的左右子树均非空。
    在这里插入图片描述
    解决方法:把未知问题转换成已知问题:待删除元素是10,先找10这个节点右子树中的最小值(也可以找左子树中的最大值):把右子树中的最小值12赋值到10这个元素的位置,由于12是右子树的最小值,此时赋值之后,二叉搜索树仍然符合条件,接下来删除12这个节点就可以了,下面这个12要么没有子树,要么只有右子树(此时就转换成了第一个情况)

  • 待删除元素是父节点的右子树,待删除元素左右节点均非空。方法:此处的情况和紧挨着上面的情况完全相同,也是找到待删除元素右子树中的最小值(或者找待删除元素左子树中的最大值),把这个值赋值到待删除节点,再删除下面的刚才找到的最小值。

【注意】:不应该修改二叉搜索树中的key,value是允许修改的。如果实在需要改key,可以删掉之前的记录,重新插入个新的记录。
代码实现:

public class BinarySearchTree {
    public static class Node {
        int key;
        Node left;
        Node right;

        public Node(int key) {
            this.key = key;
        }
    }
    private Node root = null;//这是根节点,root为null的时候表示这是一个空树。
    public Node find(int key) {//查找key是否在树中存在,如果存在返回对应的Node
        Node cur = root;
        while (cur != null) {
            if (key < cur.key) {
                //就去左子树中找
                cur = cur.left;
            } else if (key > cur.key) {
                //就去右子树中找
                cur = cur.right;
            } else {
                //走到这一步就是相等,找着了
                return cur;
            }
        }
        //循环结束了,也没找到,说明key就不存在,返回null
        return null;
    }
    public boolean insert(int key) {
       /*二叉搜索树中不允许存在相同key的元素的,如果发现新插入的key重复了,那就插入失败,返回false
       * 插入成功返回true*/
        if (root == null) {
            //当前如果为空树,直接让root指向key对应的新节点即可。
            root = new Node(key);
            return true;
        }
        //和查找类似,需要先找到合适的位置,再插入元素
        Node cur = root;//先从根节点出发
        Node parent = null;//让parent始终指向cur的父节点。
        while (cur != null) {
            if (key < cur.key) {//往左走
                parent = cur;
                cur = cur.left;
            } else if(key > cur.key){//往右走
                parent = cur;
                cur = cur.right;
            }else {
                //到这里说明当前找到的某个元素和带插入元素的值相同,那么插入失败,返回false
                /*如果当前树存的只是key,发现相同的key就会认为插入失败,如果当前树存放的是键值对,发现相同的key就修改值即可*/
                return false;
            }
        }
        //当循环结束走到这,cur就指向null,当前元素就要插入到parent的子树位置,但是具体要插入到parent的左子树
        //还是右子树,就拿key和parent再比较一次就行了
        if (key < parent.key) {
            //插入到parent的左侧
            parent.left = new Node(key);
        } else {
            parent.right = new Node(key);
        }
        return true;
    }
    /*删除成功返回true,删除失败返回false
    * key在树中存在,就删除成功
    * key在树中不存在,就删除失败*/
    public boolean remove(int key) {
        /*先找到要删除节点的位置,再进行具体的删除,找到这个待删除元素后,再去判定是a~f这六种情况中的哪一种情况*/
        Node cur = root;
        Node parent = null;
        while (cur != null) {
            if (key < cur.key) {
                parent = cur;
                cur = cur.left;
            } else if (key > cur.key) {
                parent = cur;
                cur = cur.right;
            } else {
                //到这一步就是找到了要删除的元素,就是cur指向的节点
                removeNode(parent,cur);//在这个方法中去判定a~f这六种情况并进行删除
                return true;
            }

        }
        return false;//到这一步说明当前的k,并没有被找到,直接返回false即可。
    }

    private void removeNode(Node parent, Node cur) {
        if (cur.left == null) {
            //1.要删除的元素没有左子树
            if (cur == root) {
                //1.1如果要删除的节点为root
                root = cur.right;
            } else if (cur == parent.left) {
                //1.2 cur 是parent的左子树,对应画图板的情况 a
                parent.left = cur.right;
            } else {
                //1.3 cur是parent的右子树,对应画图板的情况 b
                parent.right = cur.right;
            }
        } else if (cur.right == null) {
            //2.要删除的元素没有右子树
            if (cur == root) {
                //2.1如果要删除的节点是root
                root = cur.left;
            } else if (cur == parent.left) {
                //2.2要删除节点是父节点的左子树,对应画图板的情况c
                parent.left = cur.left;
            } else {
                //2.3要删除接点是父节点的右子树
                parent.right = cur.left;
            }
        } else {
            //3.当前要删除节点有两个子树,对应画图板的e 和 f
            //(1)先找到右子树中的最小元素(替罪羊)
            Node goatParent = cur;//goatParent为替罪羊节点的父节点
            Node scapeGoat = cur.right;//替罪羊节点
            while (scapeGoat.left != null) {
                goatParent = scapeGoat;
                scapeGoat = scapeGoat.left;
            }
            //(2)把刚才找到的替罪羊的值赋给待删除节点
            cur.key = scapeGoat.key;
            //(3)删除替罪羊节点
            //替罪羊节点一定没有左子树(和情况a 和 b类似)
            if (scapeGoat == goatParent.left) {
                goatParent.left = scapeGoat.right;
            } else {
                goatParent.right = scapeGoat.right;
            }
        }
    }
public void preOrder(Node root) {//先序遍历
        if(root == null) {
            return;
        }
    System.out.print(root.key + "  ");
        preOrder(root.left);
        preOrder(root.right);
}
public void inOrder(Node root) {//中序遍历
        if (root == null) {
            return;
        }
        inOrder(root.left);
        System.out.print(root.key + " ");
        inOrder(root.right);
}
    public static void main(String[] args) {
        BinarySearchTree tree = new BinarySearchTree();
        tree.insert(9);
        tree.insert(5);
        tree.insert(2);
        tree.insert(3);
        tree.insert(4);
        tree.insert(6);
        tree.insert(1);
         //为了查看树的结构,打印出树先序和中序遍历结果
        tree.preOrder(tree.root);
        System.out.println();
        tree.inOrder(tree.root);
        System.out.println();
        Node cur = tree.find(1);
        System.out.println(cur.key);
        System.out.println("========");
        tree.remove(5);
        tree.preOrder(tree.root);
        System.out.println();
        tree.inOrder(tree.root);
        System.out.println();

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值