【C++】二叉搜索树
一、二叉搜索树的概念
二叉搜索树又称搜索二叉树,它或者是一棵空树,或者是具有以下性质的⼆叉树:
-
若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。
-
若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。
-
它的左右子树也分别为⼆叉搜索树。
⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。
比如: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;elseparent->_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;elsereturn 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;elseparent->_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;elseparent->_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;elseparent->_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;elseparent->_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;elsereplaceParent->_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;elseparent->_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;elsereturn 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;elseparent->_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;elseparent->_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;elsereplaceParent->_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-valuetemplate <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;elseparent->_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;elsereturn 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;elseparent->_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;elseparent->_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;elsereplaceParent->_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的类型也是stringdict.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{// 修改valueret->_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;elseparent->_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;elsereturn 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;elseparent->_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;elseparent->_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;elsereplaceParent->_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-valuetemplate <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;elseparent->_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;elsereturn 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;elseparent->_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;elseparent->_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;elsereplaceParent->_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;
//}
九、结语
本篇到这里就结束了,主要讲了二叉搜索树的基本概念以及实现逻辑,希望对大家有所帮助,祝生活愉快,我们下篇再见!