文章目录
1. 二叉搜索树是什么?
二叉搜索树(BinarySearchTree),其特点是左子树的所有节点都比根小,右子树的所有结点都比根大,根的左右子树也都是一个二叉搜索树,二叉搜索树的应用有两种:
- K模型:只存储key,比如可以用来判断单词拼写是否正确,树中存储种种单词,拼写的单词如果可以在树中找到就是拼写正确,反之错误。
- K-V模型:存储<key,value>键值对,一个key对应一个value,比如可以用来做单词翻译,key为英文,value对应其中文翻译。
2. 简单实现
二叉搜索树不支持修改节点,因为如果可以随意修改节点的值可能会影响树的性质,即不满足其性质特点,所以只有增、删、查。
2.1 K模型简单实现
2.1.1 节点定义
template<class K>
struct BSTreeNode
{
BSTreeNode<K>* _left; //左节点
BSTreeNode<K>* _right; //右节点
K _key; //值
//构造函数
BSTreeNode(const K& key)
:_left(nullptr)
, _right(nullptr)
, _key(key)
{}
};
2.1.2 插入节点(非递归)
//插入节点
bool Insert(const K& key)
{
//树中没有节点
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr; //记录cur的父结点,便于连接key
while (cur)
{
parent = cur;
//比cur小,到左边
if (cur->_key > key)
{
cur = cur->_left;
}
//比cur大,到右边
else if (cur->_key < key)
{
cur = cur->_right;
}
//存在相同的元素,返回false
else
{
return false;
}
}
//走到这就是key插入的正确位置
cur = new Node(key);
//key大插入左边
if (key > parent->_key)
{
parent->_right = cur;
}
//key小插入右边
else
{
parent->_left = cur;
}
return true;
}
插入新节点的逻辑很简单,根据二叉搜索树的性质,先遍历找到待插入的正确位置,然后连接父子节点即可。
2.1.3 插入节点(递归)
//插入节点(递归)
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
//插入节点(递归体)
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
else if (key < root->_key)
{
_InsertR(root->_left, key);
}
else if (key > root->_key)
{
_InsertR(root->_right, key);
}
else
{
return false;
}
}
递归版本的写法有点说法的,因为类的成员一般设置为私有,所以树的根节点外部是无法访问的,而递归需要传递根节点,那么解决的办法有两个,一是提供根的get接口,二是通过嵌套的方法来封装一下递归的函数体,这样外部传递参数时只需要传递key就可以了,递归体形参设置为指针的引用也有说法,在连接父子节点时,我们要找到父节点,解决的办法也有两个,一是把父节点作为形参传递,二是设置为引用,这样直接改变当前的节点就行了,因为其本身就是和父节点连接在一起的。
2.1.4 查找节点(非递归)
//查找结点
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
//key大,在右边
if (key > cur->_key)
{
cur = cur->_right;
}
//key小,在左边
else if (key < cur->_key)
{
cur = cur->_left;
}
//找到返回true
else
{
return true;
}
}
return false;
}
逻辑很简单,根据二叉搜索树的性质遍历判断其是否存在即可。
2.1.5 查找节点(递归)
//查找结点(递归)
bool FindR(const K& key)
{
return _FindR(_root, key);
}
//查找结点(递归体)
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
else if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (key < root->_key)
{
return _FindR(root->_left, key);
}
else
{
return true;
}
}
2.1.6 删除节点(非递归)
删除节点,节点存在四种情况:
- 待删除节点为叶子节点,这种情况不需要特意处理,适用于 2. 3. 的处理方法。
- 待删除节点左子树为空,这种情况的处理方法是"托孤",即将自身的右子树交给父节点。
- 待删除节点右子树为空,和 2. 相同的处理方法,只不过是将自身的左子树交给父节点。
- 待删除节点左右子树都不为空,这种情况相对于之前的最为复杂,因为存在左右子树,所以不可能把其都托付给父结点,此时首先找到待删除节点左子树中的最大值或者右子树中的最小值,将该值与待删除节点的值进行交换,这样可以在保证满足二叉搜索树的性质下删除待删除节点,然后只需要根据选择找待删除节点左子树中的最大值还是右子树中的最小值正确建立新的连接即可。
//删除节点
bool Erase(const K& key)
{
Node* parent = _root;
Node* cur = _root;
//找到删除节点
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
//找到所要删除的key,分为4中删除情况:
//1、cur是叶子节点
//2、cur左节点为空
//3、cur右节点为空
//4、cur左右节点都不为空
else
{
//1、的处理方式可以归类到2、3、
//cur左节点为空
if (cur->_left == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur; //释放删除节点的空间
}
//cur右节点为空
else if (cur->_right == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur; //释放删除节点的空间
}
//cur左右节点都不为空,删除方法有2种:
//1、找cur左子树最大的值代替cur
//2、找cur右子树最小的值代替cur
else
{
//这里采用2、
//找右子树中最小的值
Node* pmin_right = cur; //右子树中最小的值的父节点
Node* min_right = cur->_right; //右子树中最小的值
while (min_right->_left)
{
pmin_right = min_right;
min_right = min_right->_left;
}
cur->_key = min_right->_key; //交换值
//将min_right的子节点交给其父结点
if (min_right->_key > key)
{
pmin_right->_right = min_right->_right;
}
else
{
pmin_right->_left = min_right->_right;
}
delete min_right;
}
break;
}
}
return true;
}
2.1.7 删除节点(递归)
//删除节点(递归)
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
//删除节点(递归体)
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
else if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if (key < root->_key)
{
return _EraseR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* min_right = root->_right;
while (min_right->_left)
{
min_right = min_right->_left;
}
swap(root->_key, min_right->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
递归版本的 “待删除节点左子树为空” 和 “待删除节点左子树为空” 这两种情况都好处理,因为递归传递的是引用,所以只需改变连接即可,而 “待删除节点左右子树均不为空” 的情况要特别处理一下,具体处理方法为先找到待删除节点左子树中的最大值或者右子树中的最小值,将其与待删除节点进行交换,然后再根据前面的选择递归左子树或者右子树,这样待删除的值因为交换一定会变成 “待删除节点左子树为空” 和 “待删除节点左子树为空” 这两种情况的一种,而后一定也会得到处理。
2.1.8 全部代码(包括构造、析构、中序遍历、拷贝)
不是特别重点,不特别赘述
//K模型
template<class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//默认构造
BSTree()
:_root(nullptr)
{}
//拷贝构造
BSTree(const BSTree<K>& bst)
{
_root = Copy(bst._root);
}
//拷贝构造
BSTree<K>& operator=(BSTree bst)
{
swap(_root, bst._root);
return *this;
}
//增加节点
bool Insert(const K& key)
{
//树中没有节点
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr; //记录cur的父结点,便于连接key
while (cur)
{
parent = cur;
//比cur小,到左边
if (cur->_key > key)
{
cur = cur->_left;
}
//比cur大,到右边
else if (cur->_key < key)
{
cur = cur->_right;
}
//存在相同的元素,返回false
else
{
return false;
}
}
//走到这就是key插入的正确位置
cur = new Node(key);
//key大插入左边
if (key > parent->_key)
{
parent->_right = cur;
}
//key小插入右边
else
{
parent->_left = cur;
}
return true;
}
//增加节点(递归)
bool InsertR(const K& key)
{
return _InsertR(_root, key);
}
//查找结点
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
//key大,在右边
if (key > cur->_key)
{
cur = cur->_right;
}
//key小,在左边
else if (key < cur->_key)
{
cur = cur->_left;
}
//找到返回true
else
{
return true;
}
}
return false;
}
//查找结点(递归)
bool FindR(const K& key)
{
return _FindR(_root, key);
}
//删除节点
bool Erase(const K& key)
{
Node* parent = _root;
Node* cur = _root;
//找到删除节点
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
//找到所要删除的key,分为4中删除情况:
//1、cur是叶子节点
//2、cur左节点为空
//3、cur右节点为空
//4、cur左右节点都不为空
else
{
//1、的处理方式可以归类到2、3、
//cur左节点为空
if (cur->_left == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur; //释放删除节点的空间
}
//cur右节点为空
else if (cur->_right == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur; //释放删除节点的空间
}
//cur左右节点都不为空,删除方法有2种:
//1、找cur左子树最大的值代替cur
//2、找cur右子树最小的值代替cur
else
{
//这里采用2、
//找右子树中最小的值
Node* pmin_right = cur; //右子树中最小的值的父节点
Node* min_right = cur->_right; //右子树中最小的值
while (min_right->_left)
{
pmin_right = min_right;
min_right = min_right->_left;
}
cur->_key = min_right->_key; //交换值
//将min_right的子节点交给其父结点
if (min_right->_key > key)
{
pmin_right->_right = min_right->_right;
}
else
{
pmin_right->_left = min_right->_right;
}
delete min_right;
}
break;
}
}
return true;
}
//删除节点(递归)
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
//中序输出--类外调用不方便传参,通过这种方式调用
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//释放树的所有节点
void Destory()
{
_Destory(_root);
}
protected:
//增加节点(递归体)
bool _InsertR(Node*& root, const K& key)
{
if (root == nullptr)
{
root = new Node(key);
return true;
}
else if (key < root->_key)
{
_InsertR(root->_left, key);
}
else if (key > root->_key)
{
_InsertR(root->_right, key);
}
else
{
return false;
}
}
//中序输出(递归体)
void _InOrder(const Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
//查找结点(递归体)
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
else if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (key < root->_key)
{
return _FindR(root->_left, key);
}
else
{
return true;
}
}
//释放树的所有节点(递归体)
void _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
root = nullptr;
}
//删除节点(递归体)
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
else if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if (key < root->_key)
{
return _EraseR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* min_right = root->_right;
while (min_right->_left)
{
min_right = min_right->_left;
}
swap(root->_key, min_right->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
//拷贝
Node* Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* new_node = new Node(root->_key);
new_node->_left = Copy(root->_left);
new_node->_right = Copy(root->_right);
return new_node;
}
private:
Node* _root;
};
2.2 K-V模型简单实现
K-V模型与K模型无特别大区别,无非是多了value,查找规则依然是按照key来进行的,故不特别赘述实现逻辑
2.2.1 节点定义
//搜索二叉树的节点(K-V模型)
template<class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left; //左节点
BSTreeNode<K, V>* _right; //右节点
K _key; //键
V _val; //值
//构造函数
BSTreeNode(const K& key, const V& val)
:_left(nullptr)
, _right(nullptr)
, _key(key)
, _val(val)
{}
};
2.2.2 全部代码
//K-V模型
template<class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
//默认构造
BSTree()
:_root(nullptr)
{}
//拷贝构造
BSTree(const BSTree<K, V>& bst)
{
_root = Copy(bst._root);
}
//拷贝构造
BSTree<K, V>& operator=(BSTree bst)
{
swap(_root, bst._root);
return *this;
}
//增加节点
bool Insert(const K& key, const V& val)
{
//树中没有节点
if (_root == nullptr)
{
_root = new Node(key, val);
return true;
}
Node* cur = _root;
Node* parent = nullptr; //记录cur的父结点,便于连接key
while (cur)
{
parent = cur;
//比cur小,到左边
if (cur->_key > key)
{
cur = cur->_left;
}
//比cur大,到右边
else if (cur->_key < key)
{
cur = cur->_right;
}
//存在相同的元素,返回false
else
{
return false;
}
}
//走到这就是key插入的正确位置
cur = new Node(key, val);
//key大插入左边
if (key > parent->_key)
{
parent->_right = cur;
}
//key小插入右边
else
{
parent->_left = cur;
}
return true;
}
//增加节点(递归)
bool InsertR(const K& key, const V& val)
{
return _InsertR(_root, key, val);
}
//查找结点
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
//key大,在右边
if (key > cur->_key)
{
cur = cur->_right;
}
//key小,在左边
else if (key < cur->_key)
{
cur = cur->_left;
}
//找到返回true
else
{
return cur;
}
}
return nullptr;
}
//查找结点(递归)
Node* FindR(const K& key)
{
return _FindR(_root, key);
}
//删除节点
bool Erase(const K& key)
{
Node* parent = _root;
Node* cur = _root;
//找到删除节点
while (cur)
{
if (key > cur->_key)
{
parent = cur;
cur = cur->_right;
}
else if (key < cur->_key)
{
parent = cur;
cur = cur->_left;
}
//找到所要删除的key,分为4中删除情况:
//1、cur是叶子节点
//2、cur左节点为空
//3、cur右节点为空
//4、cur左右节点都不为空
else
{
//1、的处理方式可以归类到2、3、
//cur左节点为空
if (cur->_left == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_right;
}
else
{
parent->_left = cur->_right;
}
delete cur; //释放删除节点的空间
}
//cur右节点为空
else if (cur->_right == nullptr)
{
if (key > parent->_key)
{
parent->_right = cur->_left;
}
else
{
parent->_left = cur->_left;
}
delete cur; //释放删除节点的空间
}
//cur左右节点都不为空,删除方法有2种:
//1、找cur左子树最大的值代替cur
//2、找cur右子树最小的值代替cur
else
{
//这里采用2、
//找右子树中最小的值
Node* pmin_right = cur; //右子树中最小的值的父节点
Node* min_right = cur->_right; //右子树中最小的值
while (min_right->_left)
{
pmin_right = min_right;
min_right = min_right->_left;
}
cur->_key = min_right->_key; //交换值
//将min_right的子节点交给其父结点
if (min_right->_key > key)
{
pmin_right->_right = min_right->_right;
}
else
{
pmin_right->_left = min_right->_right;
}
delete min_right;
}
break;
}
}
return true;
}
//删除节点(递归)
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
//中序输出--类外调用不方便传参,通过这种方式调用
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//释放树的所有节点
void Destory()
{
_Destory(_root);
}
protected:
//增加节点(递归体)
bool _InsertR(Node*& root, const K& key, const V& val)
{
if (root == nullptr)
{
root = new Node(key, val);
return true;
}
else if (key < root->_key)
{
_InsertR(root->_left, key, val);
}
else if (key > root->_key)
{
_InsertR(root->_right, key, val);
}
else
{
return false;
}
}
//中序输出(递归体)
void _InOrder(const Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << ":" << root->_val << endl;
_InOrder(root->_right);
}
//查找结点(递归体)
Node* _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return nullptr;
}
else if (key > root->_key)
{
return _FindR(root->_right, key);
}
else if (key < root->_key)
{
return _FindR(root->_left, key);
}
else
{
return root;
}
}
//释放树的所有节点(递归体)
void _Destory(Node*& root)
{
if (root == nullptr)
{
return;
}
_Destory(root->_left);
_Destory(root->_right);
delete root;
root = nullptr;
}
//删除节点(递归体)
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
else if (key > root->_key)
{
return _EraseR(root->_right, key);
}
else if (key < root->_key)
{
return _EraseR(root->_left, key);
}
else
{
Node* del = root;
if (root->_left == nullptr)
{
root = root->_right;
}
else if (root->_right == nullptr)
{
root = root->_left;
}
else
{
Node* min_right = root->_right;
while (min_right->_left)
{
min_right = min_right->_left;
}
swap(root->_key, min_right->_key);
return _EraseR(root->_right, key);
}
delete del;
return true;
}
}
//拷贝
Node* Copy(Node* root)
{
if (root == nullptr)
{
return nullptr;
}
Node* new_node = new Node(root->_key, root->_val);
new_node->_left = Copy(root->_left);
new_node->_right = Copy(root->_right);
return new_node;
}
private:
Node* _root;
};
2.3 K模型、K-V模型简单应用场景
2.3.1 K模型
比如现在有这样一个场景,你是一个小区的住户,小区有一个地下停车场,如果你购买了车位,那么你就可以进入地下停车场停车,反之不行。
#include "BinarySearchTree.hpp"
int main()
{
BSTree<string> bst; //定义
//车牌录入
bst.Insert("xx·xxxx1");
bst.Insert("xx·xxxx2");
bst.Insert("xx·xxxx3");
string str;
//循环输入
while (cin >> str)
{
bool ret = bst.Find(str); //判断该车牌是否存在
//存在
if (ret)
{
cout << str << " 允许进入!" << endl;
}
//不存在
else
{
cout << str << " 禁止进入!" << endl;
}
}
return 0;
}
运行结果:
2.3.2 K-V模型
比如我们要实现输入一个英文单词,如果该单词存在树中就输出其中文翻译,不存在输出不存在(这里仅做演示,单词的录入肯定不多,只为达到演示效果)
#include "BinarySearchTree.hpp"
int main()
{
BSTree<string, string> bst; //定义
//单词录入
bst.Insert("apple", "苹果");
bst.Insert("dog", "狗");
bst.Insert("cat", "猫");
string str;
//循环输入
while (cin >> str)
{
auto ret = bst.Find(str); //在树中寻找该单词
//不存在
if (ret == nullptr)
{
cout << "不存在" << endl;
}
//存在
else
{
cout << str << " 中文翻译为: " << ret->_val << endl;
}
}
return 0;
}
运行结果: