二分搜索树(BST)

BST主要是用来实现查找表的功能,通过Key来找到相应的Value,类似于日常使用的字典。不仅可以高效CRUD,数据维护,还可以用来找min,max,floor,ceil,rank,select等等


^v^作者此处就不去做Key-Value的演示了,仅仅用Value来替代……

首先,需要定义一个class作为实体
public class BSTNode {
    private int num;//数据
    BSTNode leftNode;//左子树
    BSTNode rightNode;//右子树
    BSTNode parent;/*表示父节点,根节点的就为null表示,
                    主要是为了删除节点的时候能够顺利进行*/

    public BSTNode() {
        super();
    }

    public BSTNode(int num) {
        super();
        this.num = num;
        this.leftNode = null;
        this.rightNode = null;
        this.parent = null;
    }

    getset

    @Override
    public String toString() {
        。。。/*注意要把打印parent的字段去掉,不然会陷入循环模式,
                从父到子,再从子到父...这种无限读取模式*/
    }
}
接下来就是重点,CRUD(虽然说笔者此处没有改,不过读者可以自己实现,其实就是覆盖操作,也看不出什么效果)
这是定义一些全局变量:
    public static BSTNode result;
    // 查找某个值找不到的时候,用来存放最后一个节点,方便插入使用
    static Queue<BSTNode> queue = new LinkedList<BSTNode>();
遍历
深度优先如下:
    /**
     * 采用中序的方式,遍历整个tree,递归注意结束的条件
     * @param root待遍历的二叉树的根节点
     */
    public static void Traverse(BSTNode root) {

        if (root == null) {
            return;
        }

        Traverse(root.getLeftNode());
        System.out.print(root.getNum() + "  ");
        Traverse(root.getRightNode());
    }
广度优先如下:
    /**
     * 采用广度优先的方式来遍历整棵树
     * @param root要遍历的tree
     */
    public static void TraverseSpan(BSTNode root) {
        if (root == null) {
            return;
        }

        queue.offer(root);
        //因为add()和remove()方法在失败的时候会抛出异常(不推荐)
        while (!queue.isEmpty()) {
            BSTNode poll = queue.poll();
            //返回第一个元素,并在队列中删除

            if (poll.getLeftNode() != null) {//加入该节点的左节点
                queue.offer(poll.getLeftNode());
            }

            if (poll.getRightNode() != null) {//加入该节点的右节点
                queue.offer(poll.getRightNode());
            }

            System.out.print(poll.getNum() + "  ");
        }
    }
查找
查找max
    /**
     * 获得传入BST的最大值节点
     * 
     * @param root
     * @return
     */
    public static BSTNode GetMax(BSTNode root) {
        if (root == null)
            return null;

        while (root.getRightNode() != null) {
            root = root.getRightNode();
        }
        return root;
    }
查找min
    /**
     * 获得传入BST的最小值节点
     * 
     * @param root
     * @return
     */
    public static BSTNode GetMin(BSTNode root) {
        if (root == null)
            return null;

        while (root.getLeftNode() != null) {
            root = root.getLeftNode();
        }
        return root;
    }
查找任意数
/**
     * 查看平衡二叉树里面是否有要查找的目标值target
     * 
     * @param root待查找的平衡二叉树的根节点
     * @param target要查找的目标值
     * @param parent当前传入Find函数的平衡二叉树的根节点的父节点,
     *        一开始没有,就传入为null,为平衡二叉树的其他操作做铺垫
     * @return 找到与否
     */
    public static boolean Find(BSTNode root, int target, BSTNode parent) {

        if (root == null) { 
        // 遍历到了叶子节点,找不到,返回false,同时作为递归的结束条件
            result = parent;
            return false;
        }

        if (root.getNum() == target) { // Find your target
            result = root;
            return true;
        } else if (root.getNum() > target) {  
        // your target is too small
            return Find(root.getLeftNode(), target, root);
        } else { // your target is too big
            return Find(root.getRightNode(), target, root);
        }
    }
插入
    /**
     * 向BST插入一个节点
     * 
     * @param root要插入节点的BST的根节点
     * @param value待插入的值
     * @return插入是否成功,如果该数已经存在就插入失败
     */
    public static boolean Insert(BSTNode root, int value) {

        if (Find(root, value, null)) { 
        // 找到要插入的值,不能出现重复的值,所以返回false
            return false;
        }

        // 找不到的情况,此时通过上面找不到时得到的result知道要插入的点的父节点
        BSTNode nodeTemp = new BSTNode();
        nodeTemp.setNum(value);
        nodeTemp.setParent(result);

        if (result.getNum() > value) { 
        // value比node的值小,放在node的左子树
            result.setLeftNode(nodeTemp);
        } else {
            result.setRightNode(nodeTemp);
        }
        return true;
    }
删除
    /**
     * 删除任意一个节点
     * 
     * @param root待操作的BST
     * @param value欲删除的数值
     * @return删除成功返回true,否则返回false
     */
    public static boolean Delete(BSTNode root, int value) {
        if (root == null) {// 判断传入的tree是否为空
            return false;
        }

        if (root.getNum() == value) {
        // 如果目标数值是根节点,进入删除的子函数
            return DeleteNode(root);
        } else if (root.getNum() > value) {
        // 目标数值比较小就进入左子树进行再判断
            return Delete(root.getLeftNode(), value);
        } else {// 目标数值比较大就进入右子树进行再判断
            return Delete(root.getRightNode(), value);
        }
    }

    /**
     * 要删除的节点,进行具体的操作
     * 
     * @param root待操作的BST
     * @return删除成功返回true,否则返回false
     */
    public static boolean DeleteNode(BSTNode root) {
        BSTNode parent = root.getParent();
        // 通过在实体对象里面添加一个属性来简化操作

        if (root.getLeftNode() == null) {// 没有左子树的情况
            if (parent.getLeftNode() == root) {
                parent.setLeftNode(root.getRightNode());
            } else {
                parent.setRightNode(root.getRightNode());
            }
            return true;
        }

        if (root.getRightNode() == null) {// 没有右子树的情况
            if (parent.getLeftNode() == root) {
                parent.setLeftNode(root.getLeftNode());
            } else {
                parent.setRightNode(root.getLeftNode());
            }
            return true;
        }

        BSTNode getMax = GetMax(root.getRightNode());
        // 左右子树都有的情况
        if (parent.getLeftNode() == root) {
            parent.setLeftNode(getMax);
        } else {
            parent.setRightNode(getMax);
        }
        getMax.setLeftNode(root.getLeftNode());
        // 不要漏了root的左子树还有再接上去
        DeleteNode(getMax);

        return true;
    }
测试如下:

由于一开始还没写insert,所以要自己new对象,然后再自己配置他们的关系

public static void main(String[] args) {
        //new 一些对象出来
        BSTNode n1 = new BSTNode(59);
        BSTNode n2 = new BSTNode(40);
        BSTNode n3 = new BSTNode(69);
        BSTNode n4 = new BSTNode(20);
        BSTNode n5 = new BSTNode(49);
        BSTNode n6 = new BSTNode(66);
        BSTNode n7 = new BSTNode(45);
        BSTNode n8 = new BSTNode(52);
        BSTNode n9 = new BSTNode(41);
        //手动配置对象之间的关系
        n9.setParent(n7);

        n8.setParent(n5);

        n7.setParent(n5);
        n7.setLeftNode(n9);

        n6.setParent(n3);

        n5.setParent(n2);
        n5.setLeftNode(n7);
        n5.setRightNode(n8);

        n4.setParent(n2);

        n3.setParent(n1);
        n3.setLeftNode(n6);

        n2.setParent(n1);
        n2.setLeftNode(n4);
        n2.setRightNode(n5);

        n1.setLeftNode(n2);
        n1.setRightNode(n3);

        /*也可以自动注入
         * BSTNode n1 = new BSTNode(40);
         * 
         * int[] temp = new int[]{59,69,66,49,20,45,41,52}; for(int i : temp){
         * Insert(n1, i); }
         */

        Traverse(n1);//深度优先

        System.out.println();
        System.out.println("*******广度优先也可以:********");

        TraverseSpan(n1);

        /*
         * System.out.println();
         * 
         * int getMax = GetMax(n1).getNum(); System.out.println("max="+getMax);
         * 
         * int getMin = GetMin(n1).getNum(); System.out.println("min="+getMin);
         * 
         * boolean Find = Find(n1, 59, null); System.out.println(Find);
         * System.out.println(result);
         * 
         * 
         * boolean insert = Insert(n1, 65); if(insert){
         * System.out.println("****插入如下*********"); Traverse(n1); }
         * 
         * System.out.println();
         * 
         * boolean delete = Delete(n1, 49); if(delete){
         * System.out.println("*****删除后如下:**********"); Traverse(n1); }
         */
    }

引申:
1、中序遍历可以起到从小到大排序的作用,后序遍历可以用于析构函数
2、实现rank()【看某个元素的排名】的思路是,在BSTNode里面再加上一个属性,表示该节点加上其子节点的个数,通过给属性就能很容易得获取到排名,重点在于insert()和remove()函数里面如何维护该属性
3、解决插入元素如果是重复的key的情况,其实可以再增加一个属性,用来记录有多少重复的值,进而使得重复的key也可以存在
4、当待插入的数据是近乎有序的时候,该BST会退化为链表,此时可以通过运用平衡二叉树,2-3,AVL来优化


总结:
1、编写BST的过程中,一开始笔者没有使用一个parent的属性,于是在删除一个节点的地方卡住了,正是加入这个parent,其实对原来已经编写的代码没什么影响,不过,一下子就解决了删除节点的问题,所以,如果以后有什么问题可以借助这种思路,试着去增加一个属性,或者函数调用的时候,加多一个变量,可能该问题就很容易解决了。
2、递归的解决思路,什么时候走什么步骤,有什么范围,试着体验一下。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值