目录
一、概念
二叉搜索树(BST)又称二叉排序树,它或者是一棵空树 , 或者是具有以下性质的二叉树:
- 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
- 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
- 它的左右子树也分别为二叉搜索树
二、操作
1. 二叉搜索树的查找
- 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
- 最多查找高度次,走到到空,还没找到,这个值不存在
1.1 普通方式
bool Find(const K& key) {
if (_root == nullptr)
return false;
node* cur = _root;
while (cur) {
if (cur->_key < key) {
cur = cur->_right;
}
else if (cur->_key > key) {
cur = cur->_left;
}
else
return true;
}
return false;
}
1.2 递归方式
思路:如果是root为空,则返回false。
如果非空,先比较查找数key与当前root中的_key的大小,大则从右边开始查找,小则从左边开始查找,层层递归,最后直到找到或者为空返回。
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(const K& key) {
if (root == nullptr) return false;
if (root->_key < key) {
return _FindR(root->_right, key);
}
else if (root->_key > key) {
return _FindR(root->_left, key);
}
else
return true;
}
2、二叉搜索树的插入
- 树为空,则直接新增节点,赋值给root指针
- 树不空,按二叉搜索树性质查找插入位置,插入新节点(一颗二叉搜索树不能存在同样的数据)
2.1 普通
bool Insert(const K& key)
{
if (_root == nullptr) {
_root = new node(key);
return true;
}
node* parent = nullptr;
node* cur = _root;
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
}
else
return false;
}
cur = new node(key);
if (parent->_key < key) {
parent->_right = cur;
}
if (parent->_key > key) {
parent->_left = cur;
}
return true;
}
2.2 递归
思路:当我们root为空的时候,直接让root指向新建的节点。非空的时候就一直递归向下。递归的思路还是非常简单的。
bool InsertR(node* &root, const K& key) {
if (root == nullptr) {
root = new node(key);
return true;
}
if (root->_key < key) {
return InsertR(root->_right, key);
}
else if (root->_key > key) {
return InsertR(root->_left, key);
}
else
return false;
}
三、二叉搜索树的删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:a. 要删除的结点无孩子结点b. 要删除的结点只有左孩子结点c. 要删除的结点只有右孩子结点d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
- 删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
- 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
- 在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除
3.1 删除节点的左节点为空的时候:
3.2 当左右节点都不为空的时候:
从 cur 的右节点开始一直向左找到最小节点 minRight ,这时候 minRight 还可能有右节点(因为minRight最小所以一定没有了左节点),我们先把 cur 的指向的值改成 minRight 指向的值,然后让 minRight 的父节点 parent 指向自己的右节点。
bool Erase(const K& key) {
node* parent = nullptr;
node* cur = _root;
while (cur) {
if (cur->_key < key) {
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key) {
parent = cur;
cur = cur->_left;
}
else {
// 1、左边空
if (cur->_left == nullptr) {
if (cur == _root) { // 如果该节点是_root,直接将_root指向删除节点的右节点。
_root = cur->_right;
}
else { // 不是_root时,还需要将删除节点的父节点来指向删除节点的右节点。
if (parent->_left == cur) {
parent->_left = cur->_right;
}
else {
parent->_right = cur->_left;
}
}
delete cur;
}
// 2、右边空
else if (cur->_right == nullptr) {
if (cur == _root) {
_root = cur->_left;
}
else {
if (parent->_left == cur) {
parent->_left = cur->_left;
}
else {
parent->_right = cur->_left;
}
}
delete cur;
}
// 3、左右都不为空,从 cur 的右节点开始一直向左找到最小节点 minRight ,这时候 minRight 还可能有右节点(因为minRight最小所以一定没有了左节点),我们先把 cur 的指向的值改成 minRight 指向的值,然后让 minRight 的父节点 parent 指向自己的右节点。
else {
node* parent = cur;
node* minright = cur->_right;
while (minright->_left) {
parent = minright;
minright = minright->_left;
}
cur->_key = minright->_key;
if (minright == parent->_left) {
parent->_left = minRight->_right;
}
else {
parent->_right = minRight->_right;
}
delete minRight;
}
return true;
}
}
return false;
}
四、拷贝构造函数以及重载运算符=的实现
- 因为每次需要传根节点,所以我们写一个函数copy来实现拷贝功能。
- copy函数原理:左右节点向上逐级链接形成一棵越来越大的树。
- 重载运算符=号:只需要与形参交换根节点,子节点自然也就跟着根节点过来了。
BSTree(const BSTree<K>& t) {
_root = Copy(t._root);
}
BSTree<K>& operator = (BSTree<K> t) {
swap(_root, t._root);
return *this;
}
node* Copy(node* root) {
if (root == nullptr) return nullptr;
node* newroot = new node(root->_key);
newroot->_left = Copy(root->_key);
newroot->_right = Copy(root->_right);
return newroot;
}
五、析构函数
因为销毁需要递归销毁,要传参根节点,我们也需要写一个destroy函数来实现。
先递归删除左右节点,然后删除根节点。
void Destory(node* root) {
if (root == nullptr) {
return;
}
// 后序删除
Destory(root->_left);
Destory(root->_right);
delete root;
}
六、二叉搜索树的应用
1、K模型
K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型
每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对