BST树 面试题(Java)

本文深入探讨二叉搜索树(BST)的各种高级操作,包括镜像反转、区间元素查找、判断BST特性、寻找公共祖先节点等。通过递归算法解析,提供高效实现策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

求BST树的镜像反转

镜像的意思很明显,就是左右交换,举个例子:
在这里插入图片描述
镜像后:
在这里插入图片描述
检验代码编写是否正确可以通过树的中序遍历,搜索二叉树的中序遍历是一个严格的升序,镜像后是一个严格的逆序。

思路:通过递归遍历的过程,不断的交换左右子树,直到叶子节点。这样遍历完成后,所有的左右子树都被交换了一遍,就实现了镜像。

实现代码

public void mirror() {
    mirror(root);
}

private void mirror(BSTNode<T> root) {
    if(root != null) {
    	//交换root的左右子树
        BSTNode<T> node = root.getLeft();
        root.setLeft(root.getRight());
        root.setRight(node);
        //递归其左右子树
        mirror(root.getLeft());
        mirror(root.getRight());
    }
}
把bst树中满足[begin,end]区间的所有元素打印出来

eg:
在这里插入图片描述
求出满足区间[10,40]的元素

那么对应这棵树,满足条件的元素是:12,18,23,35

思路

  • 看到这个问题,想想是可以套用遍历的过程的,遍历是要访问每一个节点的,我只需要判断该节点是否在范围内,如果在范围内就打印出该元素,这样遍历完整个树,就可以得到满足条件的所有元素了。
  • 但是上面的思路太僵硬化,效率也不高,为什么呢?因为这是一个二叉搜索树,上面的思路只是用了二叉树的特点,确没有使用搜索这个特点:也就是左子树所有比该当前节点小,右子树所有的节点比当前节点大。
    要利用这个特性,就可以少去很多递归,首先需要判断当前节点root和该区间的关系,可以把关系分为三种,画图来理解:
    在这里插入图片描述
    1.begin>root,那么就要去root的右子树去找
    2.begin<=root<=end,那么当前root就在该区间中,先打印root,然后根据root把区间分为两部分递归
    3.root>end,那么就要去root的左子树去找

第一种思路就是普通的遍历,这里只给出第二种思路的代码

public void printAreaDatas(T begin,T end) {
    printAreaDatas(root,begin,end);
}

private void printAreaDatas(BSTNode<T> root, T begin, T end) {
	//遍历到叶子节点返回
	if(root == null){
         return;
    }
    
    // 只有当前节点的值大于begin的时候,才去递归root的左子树
    //如果小于,就不用递归了,减少了递归的次数
    if(root.getData().compareTo(begin) > 0){
        printAreaDatas(root.getLeft(), begin, end);
    }

    //如果root在范围内,就打印当前root节点的数据
    if(root.getData().compareTo(begin) >= 0
    && root.getData().compareTo(end) <= 0){
        System.out.print(root.getData() + " ");
    }

    // 当前节点的值小于end,才有必要继续访问当前节点的右子树
    if(root.getData().compareTo(end) < 0){
        printAreaDatas(root.getRight(), begin, end);
    }
判断一个二叉树是否是BST树(阿里)

给出根节点root,判断该树是否是一棵BST树。

有一种错误的思路:递归每一个节点,判断左孩子是否比该节点小,右孩子是否比该节点大,如果每一个节点都满足条件,那么就是一棵BST树。
这种思路是错误的,这里给出一个逻辑漏洞,看图:
在这里插入图片描述
这里的21是错误的,右孩子74比21大,21比82小,满足条件,但不是一棵BST树,所以这种思路是错误的。

思路:BST树的中序遍历是一个严格递增的序列,如果有任何位置出现问题,那么就不满足严格递增,所以从严格递增入手,利用中序遍历当前的节点与上一个遍历的节点进行比较,如果不是递增就返回false

先定义一个成员变量value,用value来保存上一个中序遍历的节点数据

    T value;

注意:为什么要定义成员变量而不是通过参数递归进去呢?
public boolean isBST(BSTNode root,T value)
因为在递归回退的过程中是记忆不了数据的,举个例子:
在这里插入图片描述
在中序遍历从18回到23的时候,虽然在递归过程中设置了当前value是18,但是回溯过程并不会知道23的上一个节点是18,而是默认在递进过程中的null

代码:

	public boolean isBST() {
        return isBST(this.root);
    }

    private boolean isBST(BSTNode<T> root) {
        if(root == null){
            return true;
        }

        // 左子树已经不满足BST树性质了,直接返回,不用继续向下递归了
        if(!isBST(root.getLeft())){
            return false;
        }

        // root.getData  value  =>
        if(value != null && value.compareTo(root.getData()) > 0){
            return false;
        }
        // 注意当前节点判断完成后,需要更新一下value值
        value = root.getData();

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

}

思路:判断当前节点和data1,data2的关系,根据所判断的关系进行递归操作。如果当前节点data在data1和data2之间,那么当前节点data肯定是最近公共祖先节点。如果不在data1和data2之间,则判断往哪一边递归即可。

代码:

	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 == null){
            return null;
        }

        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){
            return getLCA(root.getRight(), data1, data2);
        } else {
            return root.getData();
        }
    }
返回倒数第k个节点
public T getOrderValue(int k){

}

思路:这里的倒数第k个节点,是指递增序列的倒数第k个节点,这里就很自然的联想到了bst树的中序遍历,bst树的中序遍历就是一个递增序列,这时在递归时,只需要加上条件i,每递归一次,就对条件i计数,然后与k进行比较,如果相同,输出即可。

但是这里是倒数第k个节点,所以需要计算好k的值

如果递增序列是 1 2 3 4 5 6 7
要拿到倒数第2个节点,这时k=2,那么真正传入的应该是该序列的长度num 7减去k 2,然后把减得的结果传入递归参数

或者换一种思维,我中序遍历先遍历左子树,然后遍历右子树,得到的是一个递增的序列,我可以先遍历右子树,再遍历左子树,这样得到的就是一个逆序的序列。这时,直接把k传入即可。

代码:
正序找倒数第几个节点

	public T getOrderValue(int k){
		//树节点的总数
        int num = number();  
        return getOrderValue(this.root, num - k);
    }

    private int i=1;
    private T getOrderValue(BSTNode<T> root, int k) {
        if(root == null){
            return null;
        }

		//向左
        T val = getOrderValue(root.getRight(), k);
        if(val != null){
            return val;
        }
		
		//访问
        if(i++ == k)
        {
            return root.getData();
        }

		//向右
        return getOrderValue(root.getLeft(), k);
    }

逆序找正数第几个节点

	public T getOrderValue(int k){
        return getOrderValue(this.root, k);
    }
 	private int i=0;
    private T getOrderValue(BSTNode<T> root, int k) {
        if(root == null){
            return null;
        }

        T val = getOrderValue(root.getLeft(), k);
        if(val != null){
            return val;
        }

        if(i++ == k)
        {
            return root.getData();
        }

        return getOrderValue(root.getRight(), k);
    }
判断是否是子树

给定一棵树,判断该树是否是当前树的子树

public boolean isChildTree(BST<T> tree){

}

思路:判断是否是子树,肯定要同时比较当前树和给定树的每一个节点,所以递归函数应该对这两棵树同时递归,要往左都往左走,要往右走同时往右走。但是要比较首先需要找到比较的开始,也就是给定树的根在已知树中的定位,找到后,同时递归比较即可。

代码:

	public boolean isChildTree(BST<T> tree){
        BSTNode<T> cur = this.root;
        // 在当前BST树上找值为tree.root.getData()的节点
        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;
            }
        }
        if(cur == null){
            return false;
        }

        return isChildTree(cur, tree.root);
    }

    private boolean isChildTree(BSTNode<T> f, BSTNode<T> c) {
        if(f == null && c == null){
            return true;
        }

        if(f == null){
            return false;
        }

        if(c == null){
            return true;
        }

        if(f.getData().compareTo(c.getData()) != 0){
            return false;
        }

        return isChildTree(f.getLeft(), c.getLeft())
                && isChildTree(f.getRight(), c.getRight());
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值