BST -- 二叉搜索树(Binary Search Tree)

一、BST树

BST树是具有下列性质的二叉树

  • 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值;
  • 若右子树不为空,则右子树上所有结点的值都大于根结点的值;
  • 它的左右子树也分别为BST树;

二、定义BST树

首先定义一个BST树的数据结构

/**
 * BST树的实现
 * @param <T>
 */
class BST<T extends Comparable<T>> {
    private BSTNode<T> root; //指向根节点
    public BST() { //BST树的初始化
        this.root = null;
    }
    //增删查等方法
}
/**
 * BST树的节点类型
 * @param <T>
 */
class BSTNode<T extends Comparable<T>> {
    private T data; //数据域
    private BSTNode<T> left; //左孩子
    private BSTNode<T> right; //右孩子

    public BSTNode(T data , BSTNode<T> leftChild , BSTNode<T> rightChild) {
        this.data = data;
        this.left = leftChild;
        this.right = rightChild;
    }
    public BSTNode(T data) {
        this.data = data;
        this.left = null;
        this.right = null;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public BSTNode<T> getLeft() {
        return left;
    }

    public void setLeft(BSTNode<T> left) {
        this.left = left;
    }

    public BSTNode<T> getRight() {
        return right;
    }

    public void setRight(BSTNode<T> right) {
        this.right = right;
    }
}

三、BST树的相关操作

1.BST树的插入

非递归插入

    public void non_insert(T data) {
        // 1、判断root根节点是否为null,null则说明BST树为空,直接让root指向
        if (this.root == null) {
            this.root = new BSTNode<>(data);
            return;
        }
        //BST树不为null,则从根节点开始寻找一个合适的位置放置新元素
        BSTNode<T> cur = this.root; //用cur遍历查找BST树
        BSTNode<T> parent = null; //parent是cur的父亲节点
        while (cur != null) {
            if (cur.getData().compareTo(data) > 0) { //cur.getData() > data
                parent = cur;
                cur = cur.getLeft();
            } else if (cur.getData().compareTo(data) < 0) { //cur.getData() < data
                parent = cur;
                cur = cur.getRight();
            } else { //cur.getData() ==  data,说明BST树中存在data的元素节点
                return;
            }
        }
        //结束上面的循环说明cur == null,生成新的节点,将新节点写入其父亲节点parent的孩子域
        if (parent.getData().compareTo(data) > 0) { //parent.getData > data ,新节点应为parent的左孩子
            parent.setLeft(new BSTNode<T>(data));
        } else { //data大于父亲节点,应为parent的右孩子
            parent.setRight(new BSTNode<T>(data));
        }
    }

递归插入

    //BST树的递归插入
    public void insert(T data) {
        this.root == insert(this.root, data);
    }
    //以root为起始节点,寻找合适位置插入data,并把插入好的二叉树的根节点返回
    private BSTNode<T> insert(BSTNode<T> root, T data) {
        if (root == null) { //说明找到了data插入的位置,在这里创建结点
            return new BSTNode<>(data);
        }
        if (root.getData().compareTo(data) > 0) {
            root.setLeft(insert(root.getLeft(), data));
        } else if (root.getData().compareTo(data) < 0){
            root.setRight(insert(root.getRight(), data));
        } else {
            return null;
        }
        return root; //将当前节点返回到父节点的函数调用中
    }

2.BST树的删除

非递归删除

	public void non_remove(T data) {
        if (this.root == null) {
            return;
        }
        //1、寻找待删除的节点
        BSTNode<T> cur = this.root;
        BSTNode<T> parent = null; //待删除节点的父亲节点

        while (cur != null) {
            if (cur.getData().compareTo(data) > 0) { //cur.getData > data,应该去cur的左子树寻找
                parent = cur;
                cur = cur.getLeft();
            } else if (cur.getData().compareTo(data) < 0) { //cur.getData < data,则去cur的左子树中寻找待删除节点
                parent = cur;
                cur = cur.getRight();
            } else { //cur就是需要删除的节点,跳出循环
                break;
            }
        }
        //循环结束:1、cur就是需要删除的节点  2、cur == null,则说明BST树中不存在值为data的节点
        if (cur == null) {
            System.out.println("BST树中不存在值为data的节点");
            return;
        }
        //2、判断删除节点是否有两个孩子,如果有,用前驱的值替代
        if (cur.getLeft() != null && cur.getRight() != null) {
            BSTNode<T> old = cur; //old 记录要删除的结点
            parent = cur;
            cur = cur.getLeft(); //此时cur是要寻找old节点的前驱结点,进入当前节点的左子树
            while (cur.getRight() != null) { //寻找old和其父亲节点
                parent = cur;
                cur = cur.getRight(); //cur寻找左子树中值最大的节点
            }
            //循环结束说明cur是old的前驱结点
            old.setData(cur.getData()); //用前驱节点的值覆盖待删除节点old的值
        }
        //3、删除只有一个孩子的结点或者没有孩子的节点(视作其只有一个孩子节点null)
        //事实上第二步中的cur的值覆盖old的值后,cur节点也还没有被删除
        BSTNode<T> child = cur.getLeft();
        if (child.getLeft() == null) {
            child.getRight();
        } //child已经指向cur唯一的孩子节点了

        if (parent == null) { //要删除的是根节点( cur == this.root , parent == null )
            this.root = child;
        } else {
            //判断cur跟parent的关系
            if (cur == parent.getLeft()) { //cur是parent的左孩子
                parent.setLeft(child);
            } else { //cur是parent的右孩子
                parent.setRight(child);
            }
        }
    }

递归删除

    //递归删除
    public void remove(T data) {
        this.root == remove(this.root, data);
    }

    private BSTNode<T> remove(BSTNode<T> root, T data) {
        if (root != null) {
            return null;
        }
        if (root.getData().compareTo(data) > 0) {
            root.setLeft(remove(root.getLeft(), data));
        } else if (root.getData().compareTo(data) < 0) {
            root.setRight(remove(root.getRight(), data));
        } else { //找到要删除的结点了
            if (root.getLeft() != null && root.getRight() != null) { //root有两个孩子结点,则用其前驱的值替代root的值,再删除前驱
                BSTNode<T> pre = root;
                pre = pre.getLeft();
                while (pre.getRight() != null) {
                    pre = pre.getRight();
                }
                root.setData(pre.getData());
                root.setLeft(remove(root.getLeft(), pre.getData()));
            } else { //root只有一个孩子结点(叶子节点看作有一个为null的孩子结点),将其孩子结点直接返回给父节点的函数调用
                if (root.getLeft() != null) {
                    return root.getLeft();
                } else if (root.getRight() != null) {
                    return root.getRight();
                } else {
                    return null;
                }
            }
        }
        return root;
    }

3.查询BST树中是否存在值为data的结点

非递归方式查找

    //BST树的查询值为data的值是否存在,存在返回true,不存在返回false
    public boolean non_reserch(T data) {
        if (this.root == null) {
            return false;
        }
        BSTNode<T> cur = this.root;
        while (cur != null) {
            if (cur.getData().compareTo(data) > 0) {
                cur = cur.getLeft();
            } else if (cur.getData().compareTo(data) < 0) {
                cur = cur.getRight();
            } else {
                return true;
            }
        }
        System.out.println("BST树中未找到值为data的节点");
        return false;
    }

递归方式查找

	public boolean reserch(T dada) {
        return reserch(this.root , dada);
    }

    private boolean reserch(BSTNode<T> root, T dada) {
        if (root == null) {
            return false;
        }
        if (root.getData().compareTo(dada) > 0) {
            return reserch(root.getLeft(), dada);
        } else if (root.getData().compareTo(dada) < 0) {
            return reserch(root.getRight(), dada);
        } else {  // root.getData().compareTo(dada) == 0 找到值为data的结点
            return true;
        }
    }

4.返回BST树中所有结点的个数(递归方式)

    //返回BST树中所有节点的个数
    public int number() {
        return number(this.root);
    }
    //以root为根节点计算BST树中的节点数
    private int number(BSTNode<T> root) {
        int num = number(root.getLeft()) + number(root.getRight()) + 1;
        return num;
    }

5.计算BST树的高度(递归方式)

    //计算BST树的高度
    public int level() {
        return level(this.root);
    }
    //计算以root为根节点的BST树的高度
    public int level(BSTNode<T> root) {
        if (root == null) {
            return 0;
        } else {
            int left = level(root.getLeft()) + 1;
            int right = level(root.getRight()) + 1;
            return left > right ? left : right;
        }
    }

6.BST树的遍历

前序遍历(VLR)

    //递归方式前序遍历BST树的API   
    public void preOrder() {
        System.out.println("递归方式前序遍历BST树");
        preOrder(this.root);
        System.out.println();
    }
    //前序遍历的递归实现
    private void preOrder(BSTNode<T> root) {
        if (root != null) {
            System.out.print(root.getData() + " ");
            preOrder(root.getLeft());
            preOrder(root.getRight());
        }
    }

中序遍历(LVR)

    //递归方式中序遍历BST树的API   
    public void inOrder() {
        System.out.println("递归方式中序遍历BST树");
        inOrder(this.root);
        System.out.println();
    }
    //中序遍历的递归实现
    private void inOrder(BSTNode<T> root) {
        if (root != null) {
            inOrder(root.getLeft());
            System.out.print(root.getData() + " ");
            inOrder(root.getRight());
        }
    }

后序遍历(RVL)

    //递归方式后序遍历BST树的API   
    public void postOrder() {
        System.out.println("递归方式后序遍历BST树");
        postOrder(this.root);
        System.out.println();
    }
    //后序遍历的递归实现
    private void postOrder(BSTNode<T> root) {
        if (root != null) {
            postOrder(root.getLeft());
            postOrder(root.getRight());
            System.out.print(root.getData() + " ");
        }
    }

层序遍历

    //层序遍历BST树
    public void levelOrder() {
        System.out.println("递归层序遍历");
        int high = level(this.root); //level()是求BST树高度的函数
        for (int i = 1;i < high;i++) {
            levelOrder(this.root,i);
    }
        System.out.println();
    }
    //层序遍历的递归实现
    private void levelOrder(BSTNode<T> root , int i) {
        if (root != null) {
            if (i == 1) {
                System.out.print(root.getData() + " ");
                return;
            }
            levelOrder(root.getLeft(), i-1);
            levelOrder(root.getRight(), i-1);
        }
    }

四、BST树常见的问题

1.BST树的镜像翻转

    //BST树的镜像翻转 API
    public void mirror() {
        mirror(this.root);
    }
    //BST镜像翻转递归实现
    private void mirror(BSTNode<T> root) {
        if (root == null) {
            return;
        }
        BSTNode<T> tmp = root.getLeft(); //定义tmp为中间结点
        root.setLeft(root.getRight()); //交换root的左右孩子结点
        root.setRight(tmp);
        mirror(root.getLeft());
        mirror(root.getRight());
    }

2.打印BST树中在[begin, end]区间的所有元素

    //打印BST树中满足[begin , end]区间的所有元素
    public void prinAreaDatas(T begin, T end) {
        prinAreaDatas(this.root, begin, end);
        System.out.println();
    }
    public void prinAreaDatas(BSTNode<T> root , T begin , T end) {
        if (root == null) { //当root为null时,结束递归
            return;
        }
        if (root.getData().compareTo(begin) > 0) { //当root的值大于begin时,再有必要递归root的左子树
            prinAreaDatas(root.getLeft(), begin, end);
        }
        //当root的值满足[begin, end]时,打印root
        if (root.getData().compareTo(begin) >= 0 && root.getData().compareTo(end) <= 0) {
            System.out.print(root.getData() + " ");
        }

        if (root.getData().compareTo(end) < 0) { //当root的值小于end时,再有必要递归root的右子树
            prinAreaDatas(root.getRight(), begin, end);
        }
    }

3.判断一个二叉树是否是BST树

乍一看这个题目,似乎只要满足root的值大于左孩子小于右孩子的值就可以了,代码如下:

    public boolean isBSTTree() {
        return isBSTTree(this.root);
    }
    private boolean isBSTTree(BSTNode<T> root) {
        if (root == null) {
            return true;
        }
        if (root.getLeft() != null && root.getData().compareTo(root.getLeft().getData()) < 0) {
            return false;
        }
        if (root.getRight() != null && root.getData().compareTo(root.getRight().getData()) > 0) {
            return false;
        }

        return isBSTTree(root.getLeft()) && isBSTTree(root.getRight());
    }

我们来测试一下

首先手动建立一个二叉树:

    public static void test(){
        BST<Integer> bst = new BST<>();
        BSTNode<Integer> node1 = new BSTNode<>(40, null, null);
        BSTNode<Integer> node2 = new BSTNode<>(20, null, null);
        BSTNode<Integer> node3 = new BSTNode<>(10, null, null);
        BSTNode<Integer> node4 = new BSTNode<>(50, null, null);
        BSTNode<Integer> node5 = new BSTNode<>(80, null, null);
        BSTNode<Integer> node6 = new BSTNode<>(30, null, null);
        BSTNode<Integer> node7 = new BSTNode<>(90, null, null);
        node1.setLeft(node2);
        node1.setRight(node5);
        node2.setLeft(node3);
        node2.setRight(node4);
        node5.setLeft(node6);
        node5.setRight(node7);
        bst.setRoot(node1);
        System.out.println(bst.isBSTTree());
    }

长这个样子,每个结点都满足root.left.data < root.data < root.right.data,但是很明显不是BST树
在这里插入图片描述
而测试的结果是这样
在这里插入图片描述

所以要明确的就是,不能只简单的判断root和两个孩子结点的大小关系,在BST树的在中序遍历中可以得到顺序的元素排列,所以只要按照中序遍历的思想,从根节点的左子树中最小的元素开始,让每个结点的值都满足大于它的直接前驱pre,即root.left.data < pre,更新pre = root.data,pre < root.right.data

    //判断一个二叉树是不是BST树
    public boolean isBSTTree() {
        T pre = null;
        return isBSTTree(this.root, pre);
    }

    //node记录的是中序遍历root的上一个结点
    private boolean isBSTTree(BSTNode<T> root, T pre) {
        if (root == null) {
            return true;
        }
        //递归root的左子树,若不满足BST树的性质,返回false
        if (!isBSTTree(root.getLeft(), pre)) {
            return false;
        }
        //递归的判断条件:若root比node的值小说明不满足BST树的性质,返回false
        if (pre != null && root.getData().compareTo(pre) < 0) {
            return false;
        }
        pre = root.getData(); //更新value
        //到这里说明root的左子树符合性质,再继续递归root的右子树
        return isBSTTree(root.getRight(), pre);
    }

4.返回两个结点的最近公共祖先

    //返回两个结点的最近公共祖先
    public T getLCA(T data1, T data2) {
        return getLCA(this.root, data1, data2);
    }

    private T getLCA(BSTNode<T> root, T data1, T data2) {
        if (root == this.root) {
            return null;
        }
        //data1和data2的值都小于root,则递归去以root左子树上查找
        if (root.getData().compareTo(data1) > 0 && root.getData().compareTo(data2) > 0) {
            return getLCA(root.getLeft(), data1, data2);
        } else if (root.getData().compareTo(data1) < 0 && root.getData().compareTo(data2) < 0) {
            //data1和data2的值都大于root,则递归去以root的右子树上查找
            return getLCA(root.getRight(), data1, data2);
        } else {
            //说明data1和data2分布在root的左右子树上,或者root的值等于data1或者data2,则root就是data1和data2的最近公共祖先
            return root.getData(); 
        }
    }

5.返回中序遍历倒数第K个结点的值

6.判断tree是不是当前BST树的一颗子树

    //判断tree是不是当前BST树的一颗子树
    public boolean isChildTree(BST<T> tree) {
        if (this.root == null || tree.root == null) {
            return false;
        }
        //目的是寻找BST树中有无与tree根节点值相同的结点
        BSTNode<T> cur = this.root;
        while (cur != null) {
            if (cur.getData().compareTo(tree.root.getData()) > 0) {
                cur = cur.getLeft();
            } else if (cur.getData().compareTo(tree.root.getData()) < 0) {
                cur = cur.getRight();
            } else {
                break;
            }
        }
        //结束循环有两种可能,遍历完BST树未找到与tree.root值相等的结点,则cur == null
        if (cur == null) {
            return false;
        }
        //找到cur.data == tree.root.data
        return isChildTree(cur, tree.root);
    }

    private boolean isChildTree(BSTNode<T> cur, BSTNode<T> node) {
        if (cur == null && node == null) { //当cur与troot都为null则说明tree与BST树结点完全相同
            return true;
        }
        if (cur == null) { //cur == null说明tree中存在BST树没有的元素,返回false
            return false;
        }
        if (node == null) { //node == null说明tree中仅有BST树中的一部分,是BST树的子树
            return true;
        }
        //判断当前结点cur和node的值是否相等,不等则返回false
        if (cur.getData().compareTo(node.getData()) != 0) { 
            return false;
        }
        //再分别递归当前节点cur和node的左子树、右子树
        return isChildTree(cur.getLeft(), node.getLeft())
                && isChildTree(cur.getRight(), node.getRight());
    }

7.根据参数传入的pre(BST树的前序遍历)和in(BST树的中序遍历)数组重建BST树

在这里插入图片描述

    //根据参数传入的pre和in数组重建BST树
    public void rebuild(T[] pre, T[] in) {
        this.root = rebuild(pre, 0, pre.length-1,
                            in, 0, in.length-1);
    }

    /**
     * @param pre 前序遍历的数组
     * @param i pre数组元素起始位置
     * @param j pre数组最后一个元素对应的位置
     * @param in 中序遍历的数组
     * @param m in数组元素起始位置
     * @param n in数组最后一个元素对应的位置
     * @return
     */
    private BSTNode<T> rebuild(T[] pre, int i, int j,
                               T[] in, int m, int n) {
        if (i > j || m > n) { //参数异常说明递归应该结束了
            return null;
        }
        BSTNode<T> node = new BSTNode<>(pre[i]); //i代表先序数组的第一个元素,以pre[i]创建根节点
        for (int k = 0; k <= m; k++) { //寻找pre[i]在in[]中的位置k
            if (pre[i].compareTo(in[k]) == 0) { 
                node.setLeft(rebuild(pre, i+1, i+k-m,
                                    in, m, k-1)); //递归创建node的左子树
                node.setRight(rebuild(pre, i+k-m+1, j,
                                    in, k+1, n)); //递归创建node的右子树
                break;
            }
        }
        return node;
    }

猜你喜欢

转载自blog.csdn.net/Daria_/article/details/94857647