赞
踩
二叉搜索树是二叉树的一种特殊形式。 二叉搜索树具有以下性质:每个节点中的值必须大于(或等于)其左侧子树中的任何值,但小于(或等于)其右侧子树中的任何值。
我们将在本章中更详细地介绍二叉搜索树的定义,并提供一些与二叉搜索树相关的习题。
完成本卡片后,你将:
**二叉搜索树(BST)**是二叉树的一种特殊表示形式,它满足如下特性:
注意:指的是任何值,即左/右子树上的所有节点。
下面是一个二叉搜索树的例子:
这段之后,我们提供了一个习题来让你验证一个树是否是二叉搜索树。 你可以运用我们上述提到的性质来判断。 前一章介绍的递归思想也可能会对你解决这个问题有所帮助。
像普通的二叉树一样,我们可以按照前序、中序和后序来遍历一个二叉搜索树。 但是值得注意的是,对于二叉搜索树,我们可以通过中序遍历得到一个递增的有序序列。因此,中序遍历是二叉搜索树中最常用的遍历方法。
在文章习题中,我们也添加了让你求解二叉搜索树的中序后继节点(in-order successor)的题目。显然,你可以通过中序遍历来找到二叉搜索树的中序后继节点。 你也可以尝试运用二叉搜索树的特性,去寻求更好的解决方案。
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
示例 1:
输入:
2
/ \
1 3
输出: true
示例 2:
输入:
5
/ \
1 4
/
3 6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。
方法1:递归
根据二叉搜索树的定义,我们要知道一个结点的左子树中结点的最大值不会超过结点的值,一个结点的右子树中结点的最小值也会大于结点的值。同时我们能按照中序顺序递归遍历子树。
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: bool isValidBST(TreeNode* root,TreeNode* left=NULL,TreeNode* right=NULL) { if(root==nullptr) return true; if(left!=nullptr&&root->val<=left->val)return false; if(right!=nullptr&&root->val>=right->val)return false; return isValidBST(root->left,left,root)&&isValidBST(root->right,root,right); } };
复杂度分析
除此之外,还有更简单高效的方法:
二叉树中序遍历,保留前驱结点与当前结点对比,是否小于当前结点,小于则为二叉树。
详解
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next() 将返回二叉搜索树中的下一个最小的数。
示例:
BSTIterator iterator = new BSTIterator(root);
iterator.next(); //返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
方法一:扁平化二叉搜索树
在计算机程序设计中,迭代器是使程序员能够遍历容器的对象。这是维基百科对迭代器的定义。当前,实现迭代器的最简单的方法是构建一个类似数组的容器。如果我们有一个数组,则我们只需要一个指针或者索引,就可以在O(1)的时间复杂度内实现函数 next() 和 hasNext()。
因此,我们要研究的第一种方法就是基于这种思想。我们将使用额外的数组,并将二叉搜索树展开存放到里面。我们想要数组的元素按升序排序,则我们应该对二叉搜索树进行中序遍历,然后我们在数组中构建迭代器函数。
算法:
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class BSTIterator { vector<int> array; int index; public: BSTIterator(TreeNode* root) { index = -1; Iterater(root); } void Iterater(TreeNode* root) { if(root==nullptr)return; Iterater(root->left); array.push_back(root->val); Iterater(root->right); return; } /** @return the next smallest number */ int next() { index++; return array[index]; } /** @return whether we have a next smallest number */ bool hasNext() { return index+1< array.size(); } }; /** * Your BSTIterator object will be instantiated and called as such: * BSTIterator* obj = new BSTIterator(root); * int param_1 = obj->next(); * bool param_2 = obj->hasNext(); */
复杂度分析
时间复杂度:构造迭代器花费的时间为 O(N),问题陈述只要求我们分析两个函数的复杂性,但是在实现类时,还要注意初始化类对象所需的时间;在这种情况下,时间复杂度与二叉搜索树中的节点数成线性关系。
空间复杂度:O(N),由于我们创建了一个数组来包含二叉搜索树中的所有节点值,这不符合问题陈述中的要求,任一函数的最大空间复杂度应为 O(h),其中 h 指的是树的高度,对于平衡的二叉搜索树,高度通常为 logN。
二叉搜索树主要支持三个操作:搜索、插入和删除。 在本章中,我们将讨论如何在二叉搜索树中搜索特定的值。
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
方法一递归
递归实现非常简单:
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if(root==nullptr)return nullptr;
if(root->val==val)return root;
return root->val>val?searchBST(root->left,val):searchBST(root->right,val);
}
};
复杂度分析
时间复杂度:O(H),其中 HH 指的是树的高度。平均情况下 O(logN),最坏的情况下 O(N)。
空间复杂度:平均情况下O(H)。最坏的情况下是O(N),是在递归过程中堆栈使用的空间。
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 保证原始二叉搜索树中不存在新值。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。
方法一:递归
算法:
class Solution { public: TreeNode* insertIntoBST(TreeNode* root, int val) { if(root==nullptr) { return new TreeNode(val); } //insert to left tree if(root->val>=val) { root->left = insertIntoBST(root->left,val); } //insert to right tree if(root->val<val) { root->right = insertIntoBST(root->right,val); } //final return value return root; } };
复杂度分析
时间复杂度:O(H),其中 HH 指的是树的高度。平均情况下 O(logN),最坏的情况下 O(N)。
空间复杂度:平均情况下O(H)。最坏的情况下是O(N),是在递归过程中堆栈使用的空间。
重要概念:后继节点和前驱结点
Successor 代表的是中序遍历序列的下一个节点。即比当前节点大的最小节点,简称后继节点。
先取当前节点的右节点,然后一直取该节点的左节点,直到左节点为空,则最后指向的节点为后继节点。
Predecessor 代表的是中序遍历序列的前一个节点。即比当前节点小的最大节点,简称前驱节点。先取当前节点的左节点,然后取该节点的右节点,直到右节点为空,则最后指向的节点为前驱节点。
方法:递归
这里有三种可能的情况:
3. 要删除的节点不是叶子节点,且没有右节点但是有左节点。这意味着它的后继节点在它的上面,但是我们并不想返回。我们可以使用它的前驱节点进行替代,然后再递归的向下删除前驱节点。
算法:
class Solution { public: //find the successor node TreeNode* Successor(TreeNode *root) { root = root->right; while(root->left!=nullptr) root = root->left; return root; } //find the precessor node TreeNode* Predecessor(TreeNode *root) { root = root->left; while (root->right != nullptr) root = root->right; return root; } TreeNode* deleteNode(TreeNode* root, int key) { if(root==nullptr)return nullptr; //search node to delete if(root->val>key)root->left = deleteNode(root->left,key); if(root->val<key)root->right = deleteNode(root->right,key); if(root->val==key) { //situation1: a leaf if(root->left==nullptr&&root->right==nullptr) { root = nullptr; } //situation2: has right child else if(root->right!=nullptr) { int succ = Successor(root)->val; root->val = succ; root->right = deleteNode(root->right,succ); } //situation3: has left child else { int pred = Predecessor(root)->val; root->val = pred; root->left = deleteNode(root->left,pred); } } return root; } };
复杂度分析
我们已经介绍了二叉搜索树的相关特性,以及如何在二叉搜索树中实现一些基本操作,比如搜索、插入和删除。熟悉了这些基本概念之后,相信你已经能够成功运用它们来解决二叉搜索树问题。
二叉搜索树的优点是,即便在最坏的情况下,也允许你在O(h)的时间复杂度内执行所有的搜索、插入、删除操作。
通常来说,如果你想有序地存储数据或者需要同时执行搜索、插入、删除等多步操作,二叉搜索树这个数据结构是一个很好的选择。
https://fungusfox.gitee.io/%E4%BB%8E%E4%BA%8C%E5%8F%89%E6%A0%91%E5%88%B0%E7%BA%A2%E9%BB%91%E6%A0%91/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。