[Algorithm] BST问题

本文介绍二叉搜索树(BST)的基本性质及其验证方法,包括两种有效的BST验证算法及BST的应用实例如搜索范围查询和迭代器实现。

BST性质:左边都比root小,右边都比root大

BST的中序遍历得到的节点访问顺序是从小到大的顺序

98. Valid Binary Search Tree: 点击打开链接

方法一

思路:先中序遍历BST拿到结果list,遍历list里元素,看当前的元素值是不是小于下一个元素值,只要有当前元素值大于等于下一个元素值的,就返回false

/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /**
     * @param root: The root of binary tree.
     * @return: True if the binary tree is BST, or false
     */
    public boolean isValidBST(TreeNode root) {
        if(root==null){
            return true;
        }
        
        List<TreeNode> result=inOrder(root);
        for(int i=0;i<result.size()-1;i++){
            if(result.get(i).val>=result.get(i+1).val){
                return false;
            }
        }
        return true;
    }
    
    private List<TreeNode> inOrder(TreeNode root){
        List<TreeNode> list=new ArrayList<>();
        
        Stack<TreeNode> stack=new Stack<>();
        TreeNode cur=root;
        while(cur!=null || !stack.isEmpty()){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.pop();
            list.add(cur);
            cur=cur.right;
        }
        return list;
    }
}
方法二

思路:递归,边遍历边比较

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public boolean isValidBST(TreeNode root) {
        return helper(root,Long.MAX_VALUE, Long.MIN_VALUE);
        
    }
    public boolean helper(TreeNode root,long maxVal,long minVal){
        if(root==null){
            return true;
        }
        
        if(root.val>=maxVal || root.val<=minVal){
            return false;
        }
        return helper(root.left,root.val,minVal) && helper(root.right,maxVal,root.val);
    }  
}
11. Search Range in Binary Search Tree:  点击打开链接
思路:中序遍历得到list,再遍历拿到list里每一个符合条件的值
/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /**
     * @param root: The root of the binary search tree.
     * @param k1 and k2: range k1 to k2.
     * @return: Return all keys that k1<=key<=k2 in ascending order.
     */
    public ArrayList<Integer> searchRange(TreeNode root, int k1, int k2) {
        ArrayList<Integer> result=new ArrayList<>();
        if(root==null){
            return result;
        }
        
        ArrayList<Integer> list=inorder(root);
        for(int i=0;i<list.size();i++){
            if(list.get(i)>=k1 && list.get(i)<=k2){
                result.add(list.get(i));
            }
        }
        return result;
    }
    
    private ArrayList<Integer> inorder(TreeNode root){
        ArrayList<Integer> result=new ArrayList<>();
        if(root==null){
            return result;
        }
        Stack<TreeNode> stack=new Stack<>();
        
        TreeNode cur=root;
        while(!stack.isEmpty() || cur!=null){
            while(cur!=null){
                stack.push(cur);
                cur=cur.left;
            }
            cur=stack.pop();
            result.add(cur.val);
            cur=cur.right;
        }
        return result;
    }
}
173.Binary Search Tree Iterator: 点击打开链接

思路:就是BST中序遍历的拆解形式

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

public class BSTIterator {
    Stack<TreeNode> stack;
    
    public BSTIterator(TreeNode root) {
        stack=new Stack<>();
        AddNodeToStack(root);
    }

    /** @return whether we have a next smallest number */
    public boolean hasNext() {
        return !stack.isEmpty();
    }

    /** @return the next smallest number */
    public int next() {
        TreeNode cur=stack.pop();
        AddNodeToStack(cur.right);
        return cur.val;
    }
    
     private void AddNodeToStack(TreeNode root){
        while(root!=null){
            stack.push(root);
            root=root.left;
        }
    }
}

/**
 * Your BSTIterator will be called like this:
 * BSTIterator i = new BSTIterator(root);
 * while (i.hasNext()) v[f()] = i.next();
 */




<think>嗯,我现在需要写一个C++函数来查找二叉搜索树(BST)中的最大键值。首先,我得回忆一下BST的性质。BST的特点是每个节点的左子树的键值都比它小,右子树的键值都比它大。所以,最大的节点应该是在树的最右下方?或者说是一直往右走直到没有右孩子为止? 用户给了一个例子,树的结构是: 20 / \ 10 36 / / \ 5 22 87 / \ 7 97 \ 93 按照这个结构,最大的键值应该是97吗?因为从根节点20开始,右子树是36,然后36的右孩子是87,接着87的右孩子是97,而97有一个右孩子是93,但93比97小,所以97应该是最大的。那算法需要找到这个路径。 那怎么用代码实现呢?可能需要从根节点开始,一直沿着右子节点遍历,直到某个节点的右子节点为空,那么这个节点就是最大的。 比如,函数可能长这样:传入根节点,然后循环检查右孩子是否存在,存在的话就继续往右走,否则当前节点的值就是最大的。 不过需要考虑特殊情况,比如树为空的情况。这时候应该返回什么?可能返回一个特定的值或者抛出异常,但题目中给出的例子中的树不为空,所以可能不需要处理这种情况?但作为通用函数,最好还是处理一下。 那函数的实现步骤大概是: 1. 如果根节点为空,返回错误或者某种提示。 2. 否则,初始化当前节点为根节点。 3. 循环遍历当前节点的右子节点,直到当前节点的右子节点为空。 4. 返回当前节点的键值。 这样就能得到最大的键值了。例如,在例子中,从20→36→87→97,此时97的右子节点是93,但93不是右子节点而是左?或者原题中的树结构可能显示97的右子节点是93?或者原题中的树可能有误?比如,根据题目中的结构描述: 在根节点20的右子节点是36,36的左是22,右是87。87的右是97,而97的右是93?或者可能排版问题?需要仔细看原题的结构描述。原题的结构中,在87下面有一个右子节点97,而97的右子节点是93,对吗?这样的话,97的右子节点是93。那根据BST的规则,右子节点应该比父节点大,但这里93比97小,这显然不符合BST的结构。这说明可能题目中的树结构存在错误,或者我理解错了结构? 或者可能题目中的树结构排版有问题,导致我误解了节点之间的关系。例如,97的右子节点是否真的是93?可能排版问题,例如: 可能97的右子节点是93,而93没有子节点?那这种情况下,这样的树不符合BST的规则,因为93比97小,不应该作为右子节点。这可能意味着题目中的例子可能有错误,或者我在理解结构时有误。 或者可能题目中的树结构是正确的,并且是BST,那么可能我的理解有误。例如,或许93的位置是左子节点?或者原题中的结构在排版时存在换行问题,导致子树的位置不正确? 例如,原题中的结构: 7 97 \ 93 这里可能97的右子节点是93,但这样的话,这破坏了BST的性质,因为93小于97,不能作为右子节点。这说明题目中的树结构可能存在错误,或者可能存在打字错误? 但假设题目中的树结构是正确的,并且作为BST存在,那可能我的理解有误。或者可能93是左子节点?但原题中的描述显示的是右斜线。例如,在最后几行的描述中: 97 \ 93 这表示97的右子节点是93,这显然不符合BST的规则。因此,这棵树可能不是一个合法的BST,或者可能存在排版错误。 但假设题目中的树是正确的,并且用户可能给出的例子是正确的,那么在这种情况下,当搜索最大的键值时,算法是否还能正常工作? 根据BST的规则,最大节点应该出现在最右端的节点。但是在这个例子中,当访问到97时,其右子节点是93,而93比97小,所以这不应该出现在右子树中。这说明该树的结构本身不是一个正确的BST,或者用户可能打错了结构。例如,可能97的右子节点是993,或者93的父节点是其他节点? 或者,可能我的理解有误,原题的结构可能不是这样的。例如,可能93是某个左子树的一部分? 或者,原题中的结构可能存在排版错误,导致子树的位置不正确。例如,可能93位于某个正确的位置,比如在某个右子树的末端? 在这种情况下,我需要先确认问题中的树是否是合法的BST。否则,算法可能无法正确找到最大值。但用户可能假设这棵树是正确的BST,所以需要按照BST的规则来写算法。 不管树的结构是否正确,算法本身应该正确。例如,不管输入的是否是合法的BST,算法将一直往右走直到无法继续,然后返回最后一个节点的值。但如果是BST的话,这样的算法是正确的,因为最大的节点一定在最右的位置。 假设用户给出的树是正确的BST,那么可能存在排版上的误解。例如,可能97的右子节点是93,但这不符合BST的规则,因此可能题目中的树结构存在错误。或者可能节点93的位置被错误地排版了,例如,应该是左子树? 或者可能问题中的树结构是正确的,而用户可能希望算法在这种情况下仍然正确运行,例如,即使树结构不正确,但函数仍然能找到最大的值? 但通常来说,题目中的算法应该假设输入是一个合法的BST。因此,在这种情况下,算法只要沿着右子树走到底就能找到最大值。 现在回到函数实现。假设树的结构正确,函数的大体结构是: 函数接收一个指向根节点的指针,然后循环往右走,直到当前节点的右子节点为空。此时返回当前节点的键值。 例如,在C++中: int findMax(Node* root) { if (root == nullptr) { // 处理空树的情况,例如抛出异常或者返回特定值 throw std::runtime_error("Tree is empty"); } Node* current = root; while (current->right != nullptr) { current = current->right; } return current->key; } 这样是否正确? 在用户提供的例子中,根节点是20,右子节点是36,然后36的右子节点是87,87的右子节点是97,97的右子节点是93。如果按照这个结构,算法会继续检查97的右子节点是否存在,发现存在(93),所以继续移动到93。然后检查93的右子节点是否存在,假设93的右子节点为空,那么此时返回93。但这样的话,最大的键值是93?而实际上,在该树中,最大的应该是97,但根据结构,97的右子节点是93,这显然不符合BST的规则,所以这棵树本身的结构是错误的,因此该函数在这种情况下可能返回错误的结果。 这说明,如果输入的BST结构不正确,那么该算法可能无法正确找到最大值。因此,题目中的例子可能存在错误的结构,或者用户希望即使在这种情况下,算法也能找到正确的最大值? 或者,可能我在理解树的结构时犯了错误。例如,可能97的右子节点并不存在,而是93是某个其他节点的子节点? 原题中的树结构描述可能有排版问题。例如,可能: 97的右子节点是93,或者93位于其他位置? 原题中的树结构描述如下: 97 \ 93 这可能表示97的右子节点是93。如果是这样的话,这棵BST的结构是错误的,因为右子节点的值必须大于父节点。所以该树实际上不是BST,而用户提供的例子可能存在错误。 但根据用户的要求,函数应该在该树中正确工作。这可能意味着用户提供的树实际上是正确的,可能我的理解有误。因此,需要重新分析该树的结构。 或者可能,原题中的结构排版有误,例如: 在用户给出的树结构中: 7 97 \ 93 这可能意味着节点7是某个节点的左子节点,而97是某个节点的右子节点。例如,原题的结构可能为: 根节点20的左子树是10,10的左子树是5,5的左子树是7。而根节点20的右子树是36,36的左是22,右是87。87的右是97,而97的右是93?这显然有误,因为97的右子节点93比它小,所以这不符合BST的规则。因此,这样的树结构不是合法的BST,所以该例子中的树结构不正确,从而导致算法无法正确找到最大值。 但问题要求检查算法是否能正确工作,所以可能用户给出的树结构本身是正确的,而我需要重新理解结构。或者可能,排版上存在误解,例如,节点93的位置可能属于其他分支? 或者可能,原题的结构中的节点97的右子节点实际上是93的父节点?或者可能结构中的某些层级有误? 例如,可能树的结构是这样的: 根节点20的右子树是36,36的右子树是87,87的右子树是97,而97的右子树是空,而93是某个其他节点的子节点? 或者,可能原题的结构在排版时存在缩进问题,导致子树的位置被错误理解? 例如,原题的结构可能应该被理解为: 节点5的左子节点是7,而节点87的右子节点是97,97的右子节点是93。这显然是不正确的,因为93 <97,不能作为右子节点。所以这棵树的结构是错误的,不是合法的BST。因此,当调用算法时,在这样的树中,算法会返回93,而正确的最大值应该是97。这表明,如果输入的树不是合法的BST,那么算法将无法正确找到最大值。 但问题中用户可能假设输入的树是正确的BST,所以在这种情况下,算法是正确的。 因此,函数的正确性依赖于输入的树是否是正确的BST。所以,在用户提供的例子中,可能结构是正确的,而我的理解有误。或者可能用户提供的例子中的结构是正确的BST,并且该算法能正确找到最大值。 现在需要再仔细分析用户提供的树结构: 用户给出的树结构是: 20 / \ 10 36 / / \ 5 22 87 / \ 7 97 \ 93 根据这个结构,每个节点的右子树是否都大于父节点? - 根节点20的右子节点是36,正确,36>20。 36的右子节点是87,正确,87>36。 87的右子节点是97,正确,97>87. 97的右子节点是93,错误,因为93<97,所以这导致该树不是BST。 因此,该例子中的树结构是无效的BST,因此在这种情况下,函数的行为可能不正确。 但用户的问题是要求编写一个函数,并检查该函数在例子中的树是否正常工作。然而,例子中的树结构本身不是合法的BST,因此这可能导致矛盾。 可能用户给出的例子中的树结构是正确的,只是排版错误? 例如,可能93是97的左子节点?如果是的话,那么该树的结构是合法的,但此时最大的节点仍然是97,而算法会正确找到它,因为当走到97时,其右子节点不存在,所以返回97的值。这时候函数是正确的。 所以,或许原题中的排版存在错误,导致我误认为97的右子节点是93,但实际上可能93是某个其他节点的子节点,或者原题中的结构排版有误。 或者,可能用户提供的树结构是正确的,但该树并不是一个合法的BST,此时算法是否还能正确找到最大值? 在这种情况下,算法会沿着右子节点走,直到无法继续,因此会找到93作为最大值,而实际的最大值应该是97。所以在这种情况下,算法将失败。但此时的问题在于输入的树结构不符合BST的定义,因此函数的行为未定义。 因此,函数的设计应基于输入的树是一个正确的BST。因此,只要输入是正确的BST,函数就能正确找到最大值。 综上所述,正确的算法是遍历右子树直到最后一个节点,返回该节点的键值。因此,C++函数的实现应该如此。 现在,针对用户提供的例子,如果树的结构是正确的BST,则最大的键值应该是97,而函数应该返回97。但是根据用户给出的结构,该树可能不是BST,因此函数可能返回错误的值。但用户的问题可能假设该树是正确的BST,因此,函数将正确返回97。或者,用户可能在输入结构时排版错误,导致我的误解。 因此,最终的结论是,函数的实现是正确的,只要输入的树是合法的BST。对于用户提供的例子,如果树的结构正确,则算法会正确工作。</think>要查找二叉搜索树(BST)中的最大键值,可以基于BST的特性:**右子树的所有节点值均大于当前节点**。因此,最大键值一定位于树的最右侧节点。以下是具体实现步骤: --- ### **步骤分析** 1. **边界条件**:若树为空,抛出异常或返回特定值(根据需求决定)。 2. **遍历逻辑**:从根节点出发,不断访问右子节点,直到某个节点的右子节点为空。 3. **返回值**:终止遍历时的节点即为最大值节点。 --- ### **C++ 实现代码** ```cpp #include <iostream> #include <stdexcept> struct Node { int key; Node* left; Node* right; Node(int k) : key(k), left(nullptr), right(nullptr) {} }; int findMax(Node* root) { if (root == nullptr) { throw std::runtime_error("树为空"); } Node* current = root; while (current->right != nullptr) { current = current->right; } return current->key; } // 测试代码 int main() { // 构建示例中的BST(注意:原题描述的树可能不合法,此处假设为合法BST) Node* root = new Node(20); root->left = new Node(10); root->left->left = new Node(5); root->left->left->left = new Node(7); root->right = new Node(36); root->right->left = new Node(22); root->right->right = new Node(87); root->right->right->right = new Node(97); // 假设97的右子节点为空(原题排版可能有误) try { std::cout << "最大键值为: " << findMax(root) << std::endl; // 输出97 } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } return 0; } ``` --- ### **验证算法正确性** 1. **正确BST的验证**: - 若树结构合法(如示例中`97`的右子节点为空),算法会正确返回`97`。 - 若树结构非法(如原题描述的`97`存在右子节点`93`),则树本身不是BST,算法不保证正确性。 2. **时间复杂度**:`O(h)`,其中`h`为树的高度。对于平衡BST,效率为`O(log n)`;最坏情况(链表状树)为`O(n)`。 --- ### **关键点总结** - 算法依赖BST的合法性。 - 始终沿右子树遍历是核心逻辑。 - 需处理空树等边界条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值