二叉搜索树

概念

基于TreeSet,TreeMap实现的,实际上用的是红黑树(比较平衡的树)
AVL树:一个绝对平衡的(左右子树高度差不大于1)的二叉搜索树叫AVL树。
定义:

  • 若他的左子树不为空,则左子树上所有节点的值都小于根节点得值
  • 若他的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 他的左右子树也分别是二叉搜索树
  • 二叉搜索树中序遍历是一个有序数组,核心用途:用来查找元素
  • 在搜索树中, 不能修改树的key值,如果是TreeMap只能修改value的值

操作

1.查找
在这里插入图片描述

 //当前树的根节点,是一颗空树
   private Node root = null;
  //在搜索树中查找key的值,如果找到返回key所在的节点,没找到返回null
   public  Node find(int key){
       Node 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;
   }

2.插入
在二叉搜索树中,插入元素时,都是插入在叶子节点上的(尾插比较方便)
在这里插入图片描述

 //在二叉搜索树中插入元素
  //set中是不能包含重复元素的,map中的key也不能重复
    //所以这里插入k不重复则返回true,k重复则返回false
    public boolean insert(int key){
       Node cur = root;
       //parent始终指向cur的父节点,和链表类似,因为后面需要对parent进行尾插操作
       Node parent=null;
       if (root==null){
           //为空树,直接指向新节点即可
           root = new Node(key);
           return true;
       }
       //查找合适的插入位置
       while (cur != null){
           if (cur.val<key){
               //在右子树中找
               parent=cur;
               cur=cur.right;
           }else if(cur.val>key){
               //在左子树中找
               parent = cur;
               cur=cur.left;
           }
           else {
               //说明cur。key==key,此时节点重复,插入失败
               return false;
           }
       }
       //位置查找完毕,进行插入操作
       //当循环结束后,说明此时cur==null,指向该插入节点
        //但是对插入父节点的左边还是右边还需要进行判断
        if (key < parent.val){
            //插入到左子树中
            parent.left = new Node(key);
        }else if (key > parent.val){
            //插入到右子树中
            parent.right = new Node(key);
        }
        return true;
    }

3.删除
分为多种情况
第5.6种情况:把未知问题转换为已知问题,当待删除节点左右子树都不为空的时候,找待删除节点右子树的最小值(往左找到头),将最小值赋值给待删除节点,再删掉最小值节点(此时最小值一定最多只有右子树,所以最小值的删除就转换为1,2的情况)
在这里插入图片描述

 //删除二叉搜索树的指定元素key
    //如果树中存在key就删除成功true
    //不存在key就删除失败返回false
    public boolean remove(int key){
     Node cur = root;
     Node parent = null;
     //同上述查找,找到key的位置
     while (cur != null){
         if (cur.val < key){
             //在当前节点的右子树中,记得要更新parent的值
             parent = cur;
             cur = cur.right;
         }
         else if (cur.val > key ){
             //在当前节点的左子树中
             parent = cur;
             cur = cur.left;
         }
         else {
             //说明cur.val=key,找到该节点了,开始删除操作
             removeNode(parent,cur);
             return true;
         }
     }
   //循环结束说明没找到key
        return false;
    }

    private void removeNode(Node parent, Node cur) {
       //进行删除操作,列举多种情况
        //1.待删除节点的左子树为空,右子树非空
        if (cur.left==null){
            //1.1待删除节点是根节点
            if (cur == root){
                root = cur.right;
            }
            //1.2待删除元素是父节点的左子树
            else if (parent.left == cur){
                parent.left = cur.right;
            }
            //1.3待删除元素是父节点的左子树
            else if (parent.right == cur){
                parent.right = cur.right;
            }
        }
        //2.待删除节点左子树非空,右子树为空
        else if(cur.right==null){
            //2.1 待删除元素是根节点
            if (cur == root){
                root = cur.left;
            }
            //2.2待删除节点是父节点的左子树
            else if (parent.left == cur){
                parent.left = cur.left;
            }
            //2.3待删除节点是父节点的右子树
            else if (parent.right == cur){
                parent.right = cur.left;
            }
        }
        //3.待删除元素的左右子树都不为空
        else {
            //将未知问题转换为已知问题,在待删除节点的右子树中找最小值(向左找到尽头)或左子树中找最大值
            //将最小值覆盖在待删除元素上,然后删除最小值(替罪羊),此时问题就转换为上面的列举情况之一
            Node goatparent=cur;
            Node scapegoat =cur.right;
            while (scapegoat.left != null){
               goatparent=scapegoat;
               scapegoat=scapegoat.left;
            }
            //循环结束后,scapegoat里面存的就是cur的右子树的左子树的最小值,赋值给待删除节点
            cur.val=goatparent.val;
            //删除最小值
            //当替罪羊是父节点的左子树时
            //因为替罪羊已经是最小值,所以他不可能有左子树,只可能有右子树
            if (goatparent.left == scapegoat){
                goatparent.left = scapegoat.right;
            }
            //在上面循环的时候若左子树为null,那么替罪羊就是cur.right本身。
            else {
                goatparent.right =scapegoat.right;
            }
        }
    }

4.main方法验证
复习二叉树的前序中序后序遍历

  //先序遍历
      public static void preOrder(Node root){
       if (root == null){
           return;
       }
       System.out.print(root.val +" ");
       preOrder(root.left);
       preOrder(root.right);
      }

      //中序遍历
    public static void inOrder(Node root){
       if (root==null){
           return;
       }
           inOrder(root.left);
           System.out.print(root.val+ " ");
           inOrder(root.right);

    }

public static void main(String[] args){
        //1.创建一颗搜索树,
        //2.随机插入一些元素
        //3.打印前序+中序
        //4.查找树中元素
        BinarySearchTree binarySearchTree = new BinarySearchTree();
        int [] k = {9,5,2,7,3,6,8};
        for (int x:k){
            binarySearchTree.insert(x);
        }

        //为了查看到树的树型结构,可以打印其先序和中序遍历,即可知道树的结构
        System.out.println("先序遍历结果为:");
      preOrder(binarySearchTree.root);
      System.out.println();
      System.out.println("中序遍历结果为:");
      inOrder(binarySearchTree.root);

      System.out.println();
      System.out.println("找到5这个元素");
     System.out.println(binarySearchTree.find(5).val);


      System.out.println("找到100这个元素");
      System.out.println(binarySearchTree.find(100).val);

      System.out.println("==================");
      binarySearchTree.remove(5);
        System.out.println("删除5后先序遍历结果为:");
        preOrder(binarySearchTree.root);
        System.out.println();
        System.out.println("删除5后中序遍历结果为:");
        inOrder(binarySearchTree.root);
    }
}

性能分析

在最优情况下:二叉搜索树是一个完全二叉树,时间复杂度为O(logN);
在最坏情况下:二叉搜索树是一个单支树,时间复杂度为O(N);
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值