二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
上图中每颗子树都满足上述特点。
之所以称之为搜索树是因为该树非常适合用来查找某些元素,比如以上图为例,若要查找元素6,先和根节点的值5进行比较,比5大则可以直接去它的右子树中去找,因为左子树都比5小一定查找不到,然后重复上述操作,直到查到或者到空。
这时可以发现利用搜索树查找的效率相较于暴力搜索一颗树而言提高了不少,因为上图这种情况最多只会搜索该树的高度(logN)次即可知道该元素是否存在,当然这种是最理想的情况,如果是下图所示的这种极端搜索树:
它的查找效率则与暴力搜索没有区别了,它的高度次即N次。
查找效率代表了二叉搜索树中各个操作的性能,针对这种极端情况后续会介绍两种树avl和红黑树,通过反转等操作将其变的平衡,这样搜索效率始终为logN次。
由于左比跟小右比跟大,因此对二叉搜索树进行中序遍历时得到的序列自然是升序排列。
//树节点的结构定义
template<class K>
class BSTreeNode {
public:
BSTreeNode(const K& key = K())
:_k(key), _left(nullptr), _right(nullptr)
{
;
}
K _k;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
};
template<class K>
class BSTree {
typedef BSTreeNode<K> Node;
public:
BSTree() :_root(nullptr) {
;
}
BSTree(const BSTree<K>& t) {
_root = _copy(t._root);
}
BSTree<K>& operator=(BSTree<K> t) {
swap(_root, t._root);
return *this;
}
~BSTree() {
destroyTree(_root);
}
//非递归插入
bool insert(const K& key) {
if (!_root) {
_root = new Node(key);
return true;
}
Node** cur = &_root;
while (*cur) {
if ((*cur)->_k < key) {
cur = &((*cur)->_right);
}
else if ((*cur)->_k > key) {
cur = &((*cur)->_left);
}
else {
return false;
}
}
*cur = new Node(key);
//需要记录当前结点的父亲节点
//Node* father = nullptr;
//Node* cur = _root;
//while (cur) {
// father = cur;
// if (cur->_k < key) {
// cur = cur->_right;
// }
// else if (cur->_k > key) {
// cur = cur->_left;
// }
// else {
// return false;
// }
//}
最后比较选择插入位置
//if (father->_k > key) {
// father->_left = new Node(key);
//}
//else {
// father->_right = new Node(key);
//}
return true;
}
//递归插入
bool insert_R(const K& key) {
return _insert_R(_root, key);
}
//非递归删除
bool erase(const K& key) {
Node* father = nullptr;
Node* cur = _root;
while (cur) {
if (cur->_k > key) {
father = cur;
cur = cur->_left;
}
else if (cur->_k < key) {
father = cur;
cur = cur->_right;
}
else {
//没有或者只有一个孩子的情况
if (cur->_left == nullptr) {
//首先需要判断cur是否为根节点
if (cur == _root) {
//因为左子树为空因此可以直接修改root
_root = _root->_right;
}
else {
//不是根节点,需要判断cur是father的左孩子还是右孩子
if (father->_left == cur) {
father->_left = cur->_right;
}
else {
father->_right = cur->_right;
}
}
}
else if (cur->_right == nullptr) {
//同样的思路
if (cur == _root) {
_root = _root->_left;
}
else {
if (father->_left == cur) {
father->_left = cur->_left;
}
else {
father->_right = cur->_left;
}
}
}
//两个孩子的情况
else {
//用替换法进行删除
//即找到一个合适的节点进行替换
//合适的节点为当前根的左子树的最大(最右)节点
//或者右子树的最小(最左)节点
//下面的做法是找到左子树的最大节点进行替换删除
Node* prev = cur;
Node* letfMax = cur->_left;
while (letfMax->_right) {
prev = letfMax;
letfMax = letfMax->_right;
}
cur->_k = letfMax->_k;
//若左子树没有右孩子需要特殊处理
if (prev == cur) {
prev->_left = letfMax->_left;
}
else {
prev->_right = letfMax->_left;
}
cur = letfMax;
}
delete cur;
return true;
}
}
return false;
}
//递归删除
bool erase_R(const K& key) {
return _erase_R(_root, key);
}
//非递归查找
bool find(const K& key) {
Node* cur = _root;
while (cur) {
if (cur->_k > key) {
cur = cur->_left;
}
else if (cur->_k < key) {
cur = cur->_right;
}
else {
return true;
}
}
return false;
}
//递归查找
bool find_R(const K& key) {
//需要子函数传递_root进行递归
return _find_R(_root, key);
}
void inorder() {
_inorder(_root);
cout << endl;
}
private:
//递归插入
//传递每个节点指针的引用
//因此修改root就是修改对应的左右指针,无需再进行判断
bool _insert_R(Node*& root, const K& key) {
if (!root) {
root = new Node(key);
return true;
}
if (root->_k > key) {
return _insert_R(root->_left, key);
}
else if (root->_k < key) {
return _insert_R(root->_right, key);
}
return false;
}
//递归删除
bool _erase_R(Node*& root, const K& key) {
if (!root) {
return false;
}
if (root->_k > key) {
return _erase_R(root->_left, key);
}
else if (root->_k < key) {
return _erase_R(root->_right, key);
}
//同样的情况
//1.没有或者只有一个孩子
//2.有两个孩子
//先保存要删除的节点
Node* del = root;
//传递的引用可以直接修改
if (root->_left == nullptr) {
root = root->_right;
}
else if (root->_right == nullptr) {
root = root->_left;
}
else {
//找到左子树的最大(最右)节点
Node* leftMax = root->_left;
while (leftMax->_right) {
leftMax = leftMax->_right;
}
root->_k = leftMax->_k;
//递归删除当前根的左子树的最右节点
//最终的子问题会回到没有或者只有一个孩子的情况
return _erase_R(root->_left, root->_k);
}
delete del;
return true;
}
//递归查找子函数
bool _find_R(Node* root, const K& key) {
if (!root) {
return false;
}
if (root->_k > key) {
return _find_R(root->_left, key);
}
return root->_k == key || _find_R(root->_right, key);
}
void _inorder(Node* root) {
if (root) {
_inorder(root->_left);
cout << root->_k << ' ';
_inorder(root->_right);
}
}
Node* _copy(Node* root) {
if (!root) return root;
Node* cproot = new Node(root->_k);
cproot->_left = _copy(root->_left);
cproot->_right = _copy(root->_right);
return cproot;
}
void destroyTree(Node* root) {
if (!root) return;
destroyTree(root->_left);
destroyTree(root->_right);
delete root;
}
private:
Node* _root;
};
对于搜索树一般有两种存储模型:
K模型,即上面实现的结构,只存储关键字K值即可,这种模型主要是用来快速检查一个事物在不在搜索树中。
比如:检查该学号的学生是否是本校的学生,做法如下:
把一个学校中所有学生的学号为key值构建一颗搜索树,在搜索树中检查该学号是否存在,若存在则说明是本校学生,否则不是。
K/V模型,每一个关键字K都对应一个值V,即
比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文
…
第一种模型对应模板库中的set容器,而第二种则是map容器
使用两个栈分别保存从根节点到目标节点路径中的每个节点,由于题目保证树中一定存在目标节点,因此两个栈里保存的路径节点中一定有它们的公共祖先。
依次保存完后,两个栈中保存的节点个数可能不同,所以若不同需要让节点多的先进行pop操作,直至删除到两个栈中的节点个数相等,最后循环判断栈顶的节点是否相同,若相同就是它们的最近公共祖先,否则一起pop直到相同为止:
class Solution {
public:
bool find(TreeNode* root, TreeNode* x, stack<TreeNode*>& st) {
if(!root) return false;
st.push(root);
if(root == x) return true;
//先去右子树去找目标节点
if(find(root->left, x, st)) return true;
//找不到再去左子树去找
if(find(root->right, x, st)) return true;
//都找不到就说明当前节点一定不是目标节点路径上的节点
//可以删除当前节点
st.pop();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
find(root, p, ppush);
find(root, q, qpush);
//删除至节点个数相同
while(ppush.size() > qpush.size()) {
ppush.pop();
}
while(ppush.size() < qpush.size()) {
qpush.pop();
}
//直到栈顶节点的值相同即为最近公共祖先
while(ppush.top() != qpush.top()) {
ppush.pop();
qpush.pop();
}
return ppush.top();
}
stack<TreeNode*> ppush, qpush;
};
class Solution {
public:
//创建全局变量prev,初始化为空
TreeNode* prev = nullptr;
void inorder(TreeNode* cur) {
if(!cur) return;
//先遍历左子树
inorder(cur->left);
//此时prev保存的就是cur的前驱结点,修改指向
cur->left = prev;
//prev为空说明它此时还没有指向任何一个结点
//只有不为空才可以让其指向它的后继结点cur
if(prev) {
prev->right = cur;
}
//递归到下一个结点后,cur就变成了前驱结点
//因此赋值给prev
prev = cur;
inorder(cur->right);
}
TreeNode* Convert(TreeNode* root) {
if(!root) return root;
inorder(root);
//最左边的结点即为链表表头
TreeNode* ret = root;
while(ret->left) {
ret = ret->left;
}
return ret;
}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> v;
if(!root) return v;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur || st.size()) {
//1.依次访问并保存根的和根的左子树
//2.第一步结束后,依次取出栈顶结点
//其若有右子树,则做法同第一步
while(cur) {
//访问根
v.push_back(cur->val);
//保存当前结点
st.push(cur);
//访问左子树
cur = cur->left;
}
//此时根和左子树都访问完毕
//取出栈顶元素,若它的右子树不为空则去遍历它的右子树
auto top = st.top();
st.pop();
//同样的方法
cur = top->right;
}
return v;
}
};
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
if(!root) return v;
stack<TreeNode*> st;
TreeNode* cur = root;
while(cur || st.size()) {
//先访问并保存左子树的结点
while(cur) {
st.push(cur);
cur = cur->left;
}
//到这里左子树访问完毕
//再访问根
auto top = st.top();
st.pop();
v.push_back(top->val);
//若当前结点有右子树
//则用相同的方法去遍历它的右子树
cur = top->right;
}
return v;
}
};
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> v;
if(!root) return v;
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* prev = nullptr;
while(cur || st.size()) {
//先访问它的左子树
while(cur) {
st.push(cur);
cur = cur->left;
}
auto top = st.top();
//左子树访问完毕后
//若当前根结点没有右子树或者右子树==prev
//都说明可以访问当前根节点
if(top->right == nullptr || top->right == prev) {
//当前结点访问完后top就变成了前驱结点
prev = top;
v.push_back(top->val);
st.pop();
}
else {
//否则去访问它的右子树
cur = top->right;
}
}
return v;
}
};