一、二叉搜索树的概念
二叉搜索树又称搜索二叉树,它或者是一棵空树,或者是具有以下性质的⼆叉树:
-
若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
-
若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
-
它的左右子树也分别为⼆叉搜索树。
⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。
比如:map / set / multimap / multiset 系列容器底层就是⼆叉搜索树,其中map/set不支持插入相等
值,multimap/multiset支持插入相等值。
二、二叉搜索树的性能分析
在二叉搜索树中,如果想要搜查到某一个数时,最多要找高度次。
下面分两种情况:
最优情况下,二叉搜索树为完全二叉树(或者接近完全⼆叉树),其时间复杂度为:
最差情况下,二叉搜索树退化为单支树(或者类似单支),其时间复杂度为:
那么像O(N)这样的效率显然是无法满足我们需求的,我们必须对最坏情况下的二叉搜索树进行控制,所以就有了⼆叉搜索树的变形,即平衡⼆叉搜索树(可以降低高度),平衡搜索树又衍生出了AVL树、红黑树、B树系列(B树、B+树、B*树)等,这几种树都可以满足查找,还有另外一种结构-哈希表,也可以进行查找。
大家先前应该学习过二分查找,这种查找效率也是很高的,它的时间复杂度是:,但它有局限性:
- 需要存储在支持下标随机访问的结构中,并且有序。
- 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。
三、二叉搜索树的具体实现
1、框架
//二叉搜索树结点信息
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key) //构造函数
:_key(key)
,_left(nullptr)
,_right(nullptr)
{}
};
//二叉搜索树(Binary Search Tree)
template <class K>
class BSTree
{
//typedef BSTNode<K> Node;
using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
//...
private:
Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};
在"//..."这个位置上进行二叉搜索树的各种功能接口的实现。
2、二叉搜索树的插入
⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,我们通常会将这两种情况分开实现,我们下面的实现以不支持插入相等的值为例进行操作,那么二叉树就遵循左子树上所有结点的值都小于根结点的值,右子树上所有结点的值都大于根结点的值。
插入的具体过程:
- 树为空:则直接新增结点,赋值给root指针。
- 树不为空:按⼆叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
- 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要一会往右走,一会往左走)
代码实现:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) //找到空位置进行插入
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false; //不允许插入相同的值
}
}
Node* newnode = new Node(key); //创建新结点
//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
//if (parent->_left == cur)
//parent->_left = newnode;
//else
//parent->_right = newnode;
//用上面这种方式进行判断是不可取的,因为parent可能是叶子节点,这时parent的左孩子为空,右孩子也为空,假设本意要插在parent的右边,结果if语句条件成立(因为此时cur是nullptr),最后插入到左边去了,那就可能会破坏二叉搜索树的特点
//正确逻辑应该这样:
if (parent->_key > key) //根据parent当前的值进行判断
parent->_left = newnode;
else
parent->_right = newnode;
return true;
}
3、二叉搜索树中序遍历
根据二叉搜索树的特点,中序遍历的效果就相当于顺序遍历的效果。
现有一个问题,中序遍历时需要传一个头结点的参数,但当前类中的头结点时私有成员,不能直接拿到,解决方法有两种:(1)单独写一个接口函数用来获取私有成员,(2)间接调用
我们这里用方法二来实现:
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
类外部直接调用InOrder即可。
我们测试一下上面的功能是否正确:
int main()
{
BSTree<int> t;
int a[] = { 8,3,1,10,1,6,4,7,14,13 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
return 0;
}
运行结果:
结果没问题,数组中两个1,最后直插入了一个,打印顺序也满足从小到大。
这里的二叉搜索树t的逻辑结构示意图如下:
4、二叉搜索树的查找
查找规则:
(1)从根开始比较 ,查找x,x比根的值大则往右边走查找,x比根值小则往左边走查找。
(2)最多查找 高度次,走到到空,还没找到,这个值不存在。
(3)如果不支持 插入相等的值,找到x即可返回。
代码实现:
//利用循环实现
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
/************************************************/
//利用递归实现
public:
bool Find(const K& key)
{
return _Find(_root, key);
}
private:
bool _Find(Node* _root, const K& key)
{
if (_root == nullptr)
return false;
bool ret1 = false;
bool ret2 = false;
if (_root->_key == key)
{
return true;
}
else if (_root->_key < key)
{
ret1 = Find(_root->_right, key);
}
else
{
ret2 = Find(_root->_left, key);
}
return ret1 || ret2;
}
(4)如果支持 插入相等的值,意味着有多个x存在,一般要求查找中序的第一个x。如下图,查找3,要找到1的右孩子的那个3返回。
要想查找到中序遍历中第一个x,就必须先在左子树找,若找到x,就接着在当前位置的左子树找,直到左子树上找不到x了,那么当前位置就是中序遍历中的第一个x。
5、二叉搜索树的删除
首先查找要删除的元素是否在⼆叉搜索树中,如果不存在,则返回false。如果查找元素存在则分以下四种情况分别处理(假设要删除的结点是N):
(1)N左右孩子均为空。
(2)N左孩子为空,右孩子不为空。
(3)N右孩子为空,左孩子不为空。
(4)N左右孩子均不为空。
针对以上四种情况的解决方案:
(1)把N结点的父亲对应孩子指针指向空,直接删除N结点(情况1可以当成情况2或情况3处理)
(2)把N结点的父亲对应孩子指针指向N的右孩子,直接删除N结点。
(3)把N结点的父亲对应孩子指针指向N的左孩子,直接删除N结点。
(4)无法直接删除N结点,因为N的两个孩子无处安放,可以用替换法删除。找N左子树的值最大结点R(最右节点)或N右子树的值最小结点R(最左节点)替代N,因为这两个结点中任意一个放到N的位置,都满足二叉搜索树的规则。替代N的意思就是将R的值赋给N,转而删除R结点,R结点又符合情况2或情况3,可以直接删除。
代码实现:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = nullptr;
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
replaceParent->_left = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
我们来测试一下:
int main()
{
BSTree<int> t;
int a[] = { 8,3,1,10,1,6,4,7,14,13 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
t.Erase(3);
t.InOrder();
return 0;
}
运行结果:
结果没有问题,但真的没有问题吗?我们现删除一下头结点8:
int main()
{
BSTree<int> t;
int a[] = { 8,3,1,10,1,6,4,7,14,13 };
for (auto e : a)
{
t.Insert(e);
}
t.InOrder();
t.Erase(3);
t.InOrder();
t.Erase(8);
t.InOrder();
return 0;
}
运行结果:
程序崩溃了。
经过调试,发现:
在删除过程中,replaceParent是空指针,用空指针去访问_left肯定会有问题。
replaceParent为什么是空指针呢?
我们结合逻辑图来分析:
现在要删除8,我们的代码逻辑是找右子树的最小结点来间接替换8,现在8的右子树最小结点是10,我们的代码逻辑是将10赋值给8,然后删除10,while循环的条件是"replace->_left",但此时"replace->_left"为空,所以while循环进不去,那么replaceParent的值就是起初给的nullptr。所以程序崩溃。但是replaceParent实际上是8这个位置结点的指针,它不为nullptr。如果要删除3,那么replace就是4,replaceParent就是6,那就应该修改replaceParen->_left的指向;如果要删除8,那么replace就是10,replaceParent就是8,那就应该修改replaceParen->_right的指向,所以不能固定修改replaceParen->_left的指向,要具体判断。所以我们需要对代码进行如下修改:
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = cur; //修改一
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
//修改二
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
运行结果:
这次就能正常删除8这个结点了, 这就是删除的整体逻辑。
注意:二叉搜索树是不支持修改的,因为修改后可能就不是二叉搜索树了,破坏了二叉搜索树的性质。性质一旦被破坏,那原来的插入、查找、删除就无从谈起了。二叉搜索树中不单单是只能存放整形类型元素,其它可以比较的类型的元素都可以存放。
四、二叉搜索树的使用场景
1、key搜索场景
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断key在不在。key的搜索场景实现的二叉树搜索树支持增删查,但是不支持修改,修改key破坏搜索树结构了。
场景1:小区无人值守车库,小区车库买了车位的业主车才能进小区,那么物业会把买了车位的业主的车牌号(字符串)录入后台系统(放入二叉搜索树中),车辆进入时会扫描车牌(在二叉树中搜索),看是否在系统中,如果在则抬杆,不在则提示非本小区车辆,无法进入。
场景2:检查一篇英文文章单词拼写是否正确,将词库中所有单词放入二叉搜索树中,读取文章中的单词,查找是否在⼆叉搜索树中,不在则波浪线标红提示。
2、key/value搜索场景
每一个关键码key,都有与之对应的值value,value可以是任意类型对象。在二叉搜索树的结构中(结点)除了需要存储key还要存储对应的value,增/删/查还是以key为关键字走二叉搜索树的规则进行比较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树支持修改,但是不支持修改key,修改key破坏搜索树结构了,可以修改value。
场景1:简单中英互译字典,树的结构中(结点)存储key(英文)和value(中文),搜索时输入英文,则同时查找到了英文对应的中文。
场景2:商场无人值守车库,入口进场时扫描车牌,记录车牌(key)和入场时间(value),出口离场时,扫描车牌(key),查找入场时间(key对应的value),用当前时间 - 入场时间计算出停车时长,计算出停车费用,缴费后抬杆,车辆离场。
场景3:统计一篇文章中单词出现的次数,读取一个单词,查找单词(key)是否存在,不存在这个说明第一次出现(单词,1),单词存在,则++单词对应的次数(value)。
我们上面实现的代码是针对key搜索场景下的,那么对key搜索场景下的代码进行稍微修改,就可以变成key/value搜索场景下的代码。
五、key搜索场景下源码
namespace key
{
//二叉搜索树结点信息
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key) //构造函数
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
//二叉搜索树(Binary Search Tree)
template <class K>
class BSTree
{
//typedef BSTNode<K> Node;
using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) //找到空位置进行插入
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false; //不允许插入相同的值
}
}
Node* newnode = new Node(key); //创建新结点
//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
if (parent->_key > key)
parent->_left = newnode;
else
parent->_right = newnode;
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = cur; //修改一
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
//修改二
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
private:
Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};
}
六、key/value搜索场景下源码
namespace key_value
{
//二叉搜索树结点信息
template<class K,class V>
struct BSTNode
{
K _key;
V _value;
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
BSTNode(const K& key,const V& value) //构造函数
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
//二叉搜索树(Binary Search Tree)
//K-key V-value
template <class K, class V>
class BSTree
{
//typedef BSTNode<K, V> Node;
using Node = BSTNode<K, V>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) //找到空位置进行插入
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false; //不允许插入相同的值
}
}
Node* newnode = new Node(key, value); //创建新结点
//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
if (parent->_key > key)
parent->_left = newnode;
else
parent->_right = newnode;
return true;
}
Node* Find(const K& key) //支持修改value,所以这里返回值为结点指针,便于修改value
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = cur; //修改一
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
//修改二
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
private:
Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};
}
针对key/value搜索场景下,我们可以写点代码来看看它的作用:
int main()
{
key_value::BSTree<string, string> dict; //key的类型是string,value的类型也是string
dict.Insert("left", "左边");
dict.Insert("right", "右边");
dict.Insert("insert", "插入");
dict.Insert("string", "字符串");
string str;
while (cin >> str)
{
auto ret = dict.Find(str);
if (ret) //如果找到就返回该结点指针
{
cout << "->" << ret->_value << endl;
}
else
{
cout << "无此单词,请重新输入" << endl;
}
}
return 0;
}
运行结果:
这样就可以达到输入英文出现对应汉语的效果,如果想要达到输入汉语输出对应英文那么只需把key和value的值互换即可。因为我们没有在dict中插入hello,所以在Find时就找不到。
这里在给出另外一种场景供大家理解:
int main()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
key_value::BSTree<string, int> countTree;
for (const auto& str : arr)
{
// 先查找水果在不在搜索树中
// 1、不在,说明水果第一次出现,则插入<水果, 1>
// 2、在,则查找到的结点中水果对应的次数++
//BSTreeNode<string, int>* ret = countTree.Find(str);
auto ret = countTree.Find(str);
if (ret == nullptr)
{
countTree.Insert(str, 1);
}
else
{
// 修改value
ret->_value++;
}
}
countTree.InOrder();
return 0;
}
运行结果:
这样就可以统计每个key出现的次数。
其实set容器底层就是key搜索场景,map容器底层就是key/value搜索场景。
set/map容器中是不含重复元素的,multiset/multimap容器中可以含重复元素。
七、其它注意事项
二叉搜索树是一个类,是类就有对象,是对象就有深浅拷贝问题。所以我们还需考虑拷贝构造、赋值重载、析构函数等等。接下来针对两种搜索场景来进行补充:
1、key
public:
//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
//所以我们要在此强制生成默认构造
//BSTree() {}
BSTree() = default; //C++11新语法
//拷贝构造
BSTree(const BSTree & t)
{
_root = Copy(t._root);
}
//赋值重载
BSTree& operator=(BSTree tmp)
{
swap(_root, tmp._root);
return *this;
}
//析构函数
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
private:
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
2、key/value
public:
//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
//所以我们要在此强制生成默认构造
//BSTree() {}
BSTree() = default; //C++11新语法
//拷贝构造
BSTree(const BSTree & t)
{
_root = Copy(t._root);
}
//赋值重载
BSTree& operator=(BSTree tmp)
{
swap(_root, tmp._root);
return *this;
}
//析构函数
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
private:
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
八、源码
1、BinarySearch.h
#pragma once
#include <iostream>
using namespace std;
namespace key
{
//二叉搜索树结点信息
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key) //构造函数
:_key(key)
, _left(nullptr)
, _right(nullptr)
{}
};
//二叉搜索树(Binary Search Tree)
template <class K>
class BSTree
{
//typedef BSTNode<K> Node;
using Node = BSTNode<K>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
//所以我们要在此强制生成默认构造
//BSTree() {}
BSTree() = default; //C++11新语法
//拷贝构造
BSTree(const BSTree & t)
{
_root = Copy(t._root);
}
//赋值重载
BSTree& operator=(BSTree tmp)
{
swap(_root, tmp._root);
return *this;
}
//析构函数
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) //找到空位置进行插入
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false; //不允许插入相同的值
}
}
Node* newnode = new Node(key); //创建新结点
//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
if (parent->_key > key)
parent->_left = newnode;
else
parent->_right = newnode;
return true;
}
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return true;
}
return false;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = cur; //修改一
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
//修改二
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
private:
Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};
}
/***********************************************************************/
namespace key_value
{
//二叉搜索树结点信息
template<class K,class V>
struct BSTNode
{
K _key;
V _value;
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
BSTNode(const K& key,const V& value) //构造函数
:_key(key)
,_value(value)
, _left(nullptr)
, _right(nullptr)
{}
};
//二叉搜索树(Binary Search Tree)
//K-key V-value
template <class K, class V>
class BSTree
{
//typedef BSTNode<K, V> Node;
using Node = BSTNode<K, V>; //C++新增的using用法,这里的using的typedef的效果一样,只是用法形式不一样
public:
//若只写拷贝构造,那么系统生成的默认拷贝构造就不再提供
//所以我们要在此强制生成默认构造
//BSTree() {}
BSTree() = default; //C++11新语法
//拷贝构造
BSTree(const BSTree& t)
{
_root = Copy(t._root);
}
//赋值重载
BSTree& operator=(BSTree tmp)
{
swap(_root, tmp._root);
return *this;
}
//析构函数
~BSTree()
{
Destroy(_root);
_root = nullptr;
}
bool Insert(const K& key, const V& value)
{
if (_root == nullptr)
{
_root = new Node(key, value);
return true;
}
Node* cur = _root;
Node* parent = nullptr;
while (cur) //找到空位置进行插入
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false; //不允许插入相同的值
}
}
Node* newnode = new Node(key, value); //创建新结点
//这时cur不确定在parent的那一边,所以要进行判断,最后插入新结点
if (parent->_key > key)
parent->_left = newnode;
else
parent->_right = newnode;
return true;
}
Node* Find(const K& key) //支持修改value,所以这里返回值为结点指针,便于修改value
{
Node* cur = _root;
while (cur)
{
if (cur->_key > key)
cur = cur->_left;
else if (cur->_key < key)
cur = cur->_right;
else
return cur;
}
return nullptr;
}
bool Erase(const K& key)
{
Node* cur = _root;
Node* parent = nullptr;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
//删除逻辑
//左为空
if (cur->_left == nullptr)
{
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
}
//右为空
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;
}
//左右都不为空
else
{
//这里用右子树的最小结点(最左结点)来代替要删除的结点
Node* replace = cur->_right;
Node* replaceParent = cur; //修改一
while (replace->_left)
{
replaceParent = replace;
replace = replace->_left;
}
//将replace结点的值赋给cur,再将replace删除即可
cur->_key = replace->_key;
//修改二
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
void InOrder()
{
_InOrder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << ":" << root->_value << endl;
_InOrder(root->_right);
}
void Destroy(Node* root)
{
if (root == nullptr)
return;
Destroy(root->_left);
Destroy(root->_right);
delete root;
}
Node* Copy(Node* root)
{
if (root == nullptr)
return nullptr;
Node* newRoot = new Node(root->_key, root->_value);
newRoot->_left = Copy(root->_left);
newRoot->_right = Copy(root->_right);
return newRoot;
}
private:
Node* _root = nullptr; //根节点指针,这里给一个缺省值,就不用显示写构造函数了
};
}
2、Test.cpp
#include "BinarySearch.h"
//int main()
//{
// key::BSTree<int> t;
// int a[] = { 8,3,1,10,1,6,4,7,14,13 };
//
// for (auto e : a)
// {
// t.Insert(e);
// }
//
// t.InOrder();
//
// t.Erase(3);
// t.InOrder();
//
// t.Erase(8);
// t.InOrder();
//
// for (auto e : a)
// {
// t.Erase(e);
// t.InOrder();
// }
//
// return 0;
//}
//int main()
//{
// key_value::BSTree<string, string> dict; //key的类型是string,value的类型也是string
// dict.Insert("left", "左边");
// dict.Insert("right", "右边");
// dict.Insert("insert", "插入");
// dict.Insert("string", "字符串");
//
// string str;
// while (cin >> str)
// {
// auto ret = dict.Find(str);
// if (ret) //如果找到就返回该结点指针
// {
// cout << "->" << ret->_value << endl;
// }
// else
// {
// cout << "无此单词,请重新输入" << endl;
// }
// }
// return 0;
//}
//int main()
//{
// string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };
// key_value::BSTree<string, int> countTree;
//
// for (const auto& str : arr)
// {
// // 先查找水果在不在搜索树中
// // 1、不在,说明水果第一次出现,则插入<水果, 1>
// // 2、在,则查找到的结点中水果对应的次数++
//
// //BSTreeNode<string, int>* ret = countTree.Find(str);
// auto ret = countTree.Find(str);
// if (ret == nullptr)
// {
// countTree.Insert(str, 1);
// }
// else
// {
// // 修改value
// ret->_value++;
// }
// }
// countTree.InOrder();
//
//
// //key_value::BSTree<string, int> copy = countTree;
// //copy.InOrder();
//
// return 0;
//}
九、结语
本篇到这里就结束了,主要讲了二叉搜索树的基本概念以及实现逻辑,希望对大家有所帮助,祝生活愉快,我们下篇再见!