C++进阶-二叉搜索树(二叉排序树)
目录
1.预备知识
2.二叉搜索树的概念
3.二叉搜索树的性能分析
4.二叉搜索树的模拟实现基本结构
5.二叉搜索树的中序遍历代码实现
6.二叉搜索树的插入的代码实现
7.二叉搜索树的查找的代码实现
8.二叉搜索树的删除的代码实现(最难且最重要)
9.不允许插入相等的值的二叉搜索树最终代码
10.运行插入相等的值的二叉搜索树的最终代码
11.总结
1.预备知识
二叉搜索树是在二叉树的一个进阶版本,里面涉及到的内容可能需要您有一定量的二叉树的基础,可以去看我的这篇博客以了解一下二叉树:
数据结构初阶-树、二叉树的讲解-CSDN博客文章浏览阅读919次,点赞14次,收藏21次。二叉树的简单讲解(实现详见下两讲)https://blog.csdn.net/2401_86446710/article/details/145887950?spm=1011.2415.3001.10575&sharefrom=mp_manage_link这篇内容整体从易到难,到最后难度可能很高,因为二叉树本身就是一个比较抽象的数据结构,所以理解的时候建议多画图等等。
2.二叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:
(1)若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值;
(2)若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值;
(3)它的左右⼦树也分别为⼆叉搜索树;
(4)⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等值,multimap/multiset⽀持插⼊相等值。
注:我们后面学习的map/set本质上是可以用AVL树实现的,但是一般情况下AVL树太复杂,红黑树实现map/set效果和AVL树差不多,但是都要有二叉搜索树的基础,所以这里的思维关联性很强,如果二叉搜索树没学明白,后面的红黑树和AVL树都是学不明白的!
这个二叉树就是二叉搜索树:
这种二叉树查找值的时候,只需要输入一个值,与根所存的值比较,如果二者相等就直接返回根结点的指针;如果根的值比查找的值大,就往根的左孩子走;如果根的值比查找的值小,就往根的右孩子走,直到走到空或者遇到与其相等的值结束。因此这种类型的二叉树查找很方便,所以这种类型的二叉树叫:二叉搜索树;又由于该树通过中序遍历的结果是升序的,所以又称为二叉排序树。
3.二叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为: log2 N;
最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为: N;
所以综合⽽⾔⼆叉搜索树增删查(二叉搜索树不能修改,因为这样会导致结构变化很大,以后我们的二叉搜索树以及二叉搜索树的变形都不涉及到修改结点的操作)时间复杂度为: O(N)。
虽然二叉搜索树可能会退化成单支树导致效率的缺失,但是这种类型的树在很多情况下已经比普通的二叉树好很多了。后续的AVL(平衡二叉树)和红黑树就是二叉搜索树的变形。
有些人可能要问了,之前我们学过的二分查找不也是满足O(log2 N)级别的查找效率吗,那用二分查找不是更好吗?
二分查找在之前固然好,但是二分查找有两大缺陷无法解决:
(1) 需要存储在⽀持下标随机访问的结构中,并且有序。
(2)插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
二叉搜索树整体还是比二分查找好多的,而且在我们日常的应用中都用的基本上是二叉搜索树以及二叉搜索树的变形,如果是100万个数据最多查找20次就可以找到某个数,10亿个数据也最多查找30次(这两个满足的条件都是在二叉搜索树平衡和近似平衡的时候)。
4.二叉搜索树的模拟实现基本结构
我们在现阶段手动实现二叉树主要是更深刻的理解二叉搜索树的实现的,以方便以后找工作的时候提供一定量的帮助,我们在这里先定义一下基本的结构(除了二叉树的删除看到的代码不一定是正确代码,其余的代码一定是正确代码,你们手动实现的时候就需要测试一下自己的对不对):
#include<iostream>
using namespace std;
template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key), _left(nullptr), _right(nullptr){}
};
template<class K>
class BSTree
{typedef BSTNode<K> Node;
private:Node* _root = nullptr;
};
这个二叉搜索树相对于原来的二叉树其实就只是多了一个模板参数而已,基本结构在这里,之后的操作将都在BSTree这个类里面完成。
5.二叉搜索树的中序遍历代码实现
在数据结构初阶阶段,我们实现中序遍历是用的一个简单的递归,先递归左子树,再打印根结点,最后递归右子树,但是我们要注意一点:中序遍历是有参数的,而我们在实际运用中是不会传递参数的,因为我们无法访问BSTree里面的_root,也就无法传递参数,所以需要借助另外一个函数来进行传参,所以最终代码如下:
//中序遍历函数的实现
void _InOrder(Node* root)
{if (root == nullptr){return;}//递归左子树_InOrder(root->_left);//这个打印是看个人习惯,可以直接打印换行cout << root->_key << " ";//递归右子树_InOrder(root->_right);
}
//辅助函数
void InOrder()
{_InOrder(_root);cout << endl;
}
6.二叉搜索树的插入的代码实现
插⼊的具体过程如下:
(1)树为空,则直接新增结点,赋值给root指针
(2)树不为空,按⼆叉搜索树性质,插⼊值⽐当前结点⼤往右⾛,插⼊值⽐当前结点⼩往左⾛,找到空位置,插⼊新结点。
(3)如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插⼊新结点。(要注意的是要保持逻辑⼀致性,插⼊相等的值不要⼀会往右⾛,⼀会往左⾛)
通过该思路,我们很容易可以写出以下代码(这是不允许相等的值插入的二叉搜索树):
//二叉搜索树的插入(不能插入相等的值)
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key == cur->_key){//直接返回flasereturn false;}else if (key < cur->_key){//左边不为空,继续插入parent = cur;cur = cur->_left;if(cur == nullptr){cur = new Node(key);parent->_left = cur;return true;}}else {parent = cur;cur = cur->_right;if(cur == nullptr){cur = new Node(key);parent->_right = cur;return true;}}}
}
以上代码可以改进一下:
//二叉搜索树的插入(不能插入相等的值)
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key == cur->_key){//直接返回flasereturn false;}else if (key < cur->_key){//左边不为空,继续插入parent = cur;cur = cur->_left;}else{parent = cur;cur = cur->_right;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;
}
以上两种写法都是没有问题的。
以下是二叉搜索树允许插入相等的值的代码实现:
//二叉搜索树的插入(允许插入相等的值)
void Insert(const K& key, int)
{if (_root == nullptr){_root = new Node(key);return;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key <= cur->_key){//如果小于或等于就往左走parent = cur;cur = cur->_left;}else{//如果大于就往右走parent = cur;cur = cur->_right;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}
}
第二个值只要传递一个int类型的值就可以完成两个函数同时存在了,如果你想单纯实现一种函数存在的话就可以把第二个形参去掉,我这里为了演示方便就两个都实现了。
有些人可能就问了,这样怎么传递参数?
你可以像我一样这样传递:
int main()
{BSTree<int> b;b.Insert(4, 7);b.Insert(3, 7);b.Insert(8, 7);b.Insert(1, 7);b.Insert(4, 7);b.Insert(6, 7);b.Insert(5, 7);b.Insert(8, 7);b.InOrder();return 0;
}
第二个实参只要为int类型的任意数都可以,但是插入的都只是第一个参数,第二个参数只要传递,并不会影响插入结果,我们运行一下代码有:
但是建议在实践中只要实现一个Insert函数就可以了,因为这样有些人就可能看不懂第二个参数的作用了,我们实现一个二叉搜索树要么就实现可以插入相等的值的二叉树,要么就实现不可以插入相等的值的二叉搜索树,我这里是演示一下,实践中最好别把两种类型全写上去了。
7.二叉搜索树的查找的代码实现
(1)从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找;
(2) 最多查找⾼度次,⾛到到空,还没找到,这个值不存在;
(3) 如果不⽀持插⼊相等的值,找到x即可返回;
(4) 如果⽀持插⼊相等的值,意味着有多个x存在,⼀般要求查找中序的第⼀个x。
这个实现比较简单,所以我就直接给代码了:
//二叉搜索树的查找(不能插入相等的值)
bool Find(const K& key)
{Node* cur = _root;while (cur){if (key == cur->_key){//直接返回truereturn true;}else if (key < cur->_key){cur = cur->_left;}else{cur = cur->_right;}}//没找到return false;
}
这个代码和插入的思想都差不多。
如果是二叉搜索树允许插入相等的值的情况下,我直接返回的是有几个key了,而不是返回true和false了,思想基本和不支持插入相等的值的一样:
//二叉搜索树的查找(允许插入相等的值)
int Find(const K& key,int)
{Node* cur = _root;int num = 0;while (cur){if (key == cur->_key){++num;cur = cur->_left;}else if(key > cur->_key){cur = cur->_right;}else{cur = cur->_left;}}return num;
}
我们用以下代码进行测试有:
int main()
{BSTree<int> b;b.Insert(4, 7);b.Insert(3, 7);b.Insert(8, 7);b.Insert(1, 7);b.Insert(4, 7);b.Insert(6, 7);b.Insert(5, 7);b.Insert(8, 7);cout << b.Find(4 , 1) << endl;return 0;
}
那么运行结果为:
注意:我们在寻找Find的值的时候不能只要相等就后面一直往左走了,还是要根据逻辑来,找到相等的值之后并不代表只能往左走了,因为找到的第一个值那么就代表它的相等的值(假设相等的值存在的情况下)就一定在这个值的左子树上面,通过这种逻辑就能找到最终的结果!
8.二叉搜索树的删除的代码实现(最难且最重要)
思路:首先查找是否在二叉搜索树中,如果不在,则返回false,如果在,那么就会有四种情况分别处理(假设要删除的结点为N):
1. 要删除结点N左右孩⼦均为空,把N结点的⽗亲对应孩⼦指针指向空,直接删除N结点(情况1可以当成2或者3处理,效果是⼀样的);
2. 要删除的结点N左孩⼦位空,右孩⼦结点不为空,把N结点的⽗亲对应孩⼦指针指向N的右孩⼦,直接删除N结点;
3. 要删除的结点N右孩⼦位空,左孩⼦结点不为空,把N结点的⽗亲对应孩⼦指针指向N的左孩⼦,直接删除N结点
4. 要删除的结点N左右孩⼦结点均不为空,⽆法直接删除N结点,因为N的两个孩⼦⽆处安放,只能⽤替换法删除。找N左⼦树的值最⼤结点R(最右结点)或者N右⼦树的值最⼩结点R(最左结点)替代N,因为这两个结点中任意⼀个,放到N的位置,都满⾜⼆叉搜索树的规则。替代N的意思就是N和R的两个结点的值交换,转⽽变成删除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){//cur是parent的左孩子if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}//这代表有左孩子且左孩子不为空else if (cur->_right == nullptr){//cur是parent的左孩子if (cur->_left == cur){parent->_left == cur->_left;}else{parent->_right = cur->_left;}}}}
}
这个代码相对于之前的几个函数的实现已经长了不知道多少了,而且还是没有补全的部分,所以我们一定要理解性记忆这个指针指向的改变。
相信很多人都发现了,如果我们删除_root,那么这样就会报错(因为这个时候的parent是nullptr,nullptr解引用没有意义),因此我们需要额外加个判断条件:如果parent为空,那么就代表是根结点,这个时候根结点就要为被替换的结点了。所以第一次修改后的结果如下:
//修改1次
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 (parent == nullptr)//替换成这样更好理解if(cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}//这代表有左孩子且左孩子不为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//cur是parent的左孩子if (parent->_left == cur){parent->_left == cur->_left;}else{parent->_right = cur->_left;}}}}}
}
有些人可能又发现了,这个代码怎么感觉可能会造成内存泄漏,所以我们还需要再进行修改,防止内存泄漏,而且我们还没有把最终的结果返回,如果到最后阶段忘记就得不偿失了,所以我们就要都添加一下:
//修改2次
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 (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子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{//cur是parent的左孩子if (parent->_left == cur){parent->_left == cur->_left;}else{parent->_right = cur->_left;}}delete cur;}return true;}}return false;
}
那么就到了最后的问题了,如何替换?
这个替换不是非常难,我们只要第一次从cur位置从右子树找最左的结点替换或者从左子树找最右结点替换即可,我的示例是从右子树找最左结点,你们可以尝试一下从左子树找最右结点,我这里就演示第一种了,所以修改后的代码为:
//第三次修改
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 (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子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{//cur是parent的左孩子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;}//记得包含头文件algorithmstd::swap(replace->_key, cur->_key);delete replace;//一定是左孩子,因为一直都是往左遍历的replaceParent->_left = nullptr;}return true;}}return false;
}
但是这不是最终代码,为什么?
如果右子树在一直在找左孩子的时候,有一个结点没有左孩子又有右孩子怎么办,这个时候难道继续遍历吗?
不行!!!因为我们如果往右遍历就会导致结构被破坏了,我们前面思想都不要改,我们主要看怎么处理,在之前的情况中,我们也遇到这种情况,这个时候我们交换两个结点的值之后,只要把replaceParent->_left指向replace->_right即可。
但是这又有一个特殊的情况:
如果我们在擦除结点的右孩子没有左孩子怎么办(即被擦除结点的右子树的根结点没有左孩子)?
所以这个时候我们不能直接:replaceParent->_left=replace->_right了,要先判断是不是replace为replaceParent的左孩子,再进行改变指向,所以最终代码为:
//最终代码
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 (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子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{//cur是parent的左孩子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;}//记得包含头文件algorithmstd::swap(replace->_key, cur->_key);if (replaceParent->_left == replace){//这样就算是replace没有右孩子也没事replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;}return true;}}return false;
}
如果运行插入相同的值的二叉树,那么我们就要注意了,最后delete完之后,我们要把cur继续作为原parent的左/右孩子,如果cur原来是_root,这个时候我们只能用parent是否是nullptr来判断,而且我们不能进入了第一层判断条件的else语句就return了,我们需要一直等到循环结束才进行返回,我们判断是否删除就可以直接定义一个bool类型的值(初始化为false),一旦进入第一层判断的else语句就置为true,其他的基本上没变(没有发生cur指向的改变的那个语句就不要改变指针的指向,那样多此一举),所以最终代码为:
//二叉搜索树的删除(允许插入相等的值)
bool Erase(const K& key, int)
{Node* cur = _root;Node* parent = nullptr;bool deleted = false;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{deleted = true;//开始删除//不要写错了,这代表有右孩子(右孩子也有可能为空)if (cur->_left == nullptr){//这个不是很容易理解//if (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;//如果parent为根结点if (parent == nullptr){cur = _root;}else{//如果先前为左孩子,则继续作为左孩子,只是原来的parent的左孩子被删了而已if (parent->_left == cur){cur = parent->_left;}//反之继续做右孩子,只是原来的parent的右孩子被删了而已else{cur = parent->_right;}}}//这代表有左孩子且左孩子不为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//cur是parent的左孩子if (cur->_left == cur){parent->_left == cur->_left;}else{parent->_right = cur->_left;}}delete cur;//如果parent为根结点if (parent == nullptr){cur = _root;}else{//如果先前为左孩子,则继续作为左孩子,只是原来的parent的左孩子被删了而已if (parent->_left == cur){cur = parent->_left;}//反之继续做右孩子,只是原来的parent的右孩子被删了而已else{cur = parent->_right;}}}else{Node* replace = cur->_right;Node* replaceParent = cur;while (replace->_left){replaceParent = replace;replace = replace->_left;}//记得包含头文件algorithmstd::swap(replace->_key, cur->_key);if (replaceParent->_left == replace){//这样就算是replace没有右孩子也没事replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;}}}return deleted;
}
9.不允许插入相等的值的二叉搜索树最终代码
#include<iostream>
#include<algorithm>
using namespace std;
template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key), _left(nullptr), _right(nullptr){}
};
template<class K>
class BSTree
{typedef BSTNode<K> Node;
public://中序遍历函数的实现void _InOrder(Node* root){if (root == nullptr){return;}//递归左子树_InOrder(root->_left);//这个打印是看个人习惯,可以直接打印换行cout << root->_key << " ";//递归右子树_InOrder(root->_right);}//辅助函数void InOrder(){_InOrder(_root);cout << endl;}//二叉搜索树的插入(不能插入相等的值)bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key == cur->_key){//直接返回flasereturn false;}else if (key < cur->_key){//左边不为空,继续插入parent = cur;cur = cur->_left;}else{parent = cur;cur = cur->_right;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}
//二叉搜索树的查找(不能插入相等的值)
bool Find(const K& key)
{Node* cur = _root;while (cur){if (key == cur->_key){//直接返回truereturn true;}else if (key < cur->_key){cur = cur->_left;}else{cur = cur->_right;}}//没找到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 (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子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{//cur是parent的左孩子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;}//记得包含头文件algorithmstd::swap(replace->_key, cur->_key);if (replaceParent->_left == replace){//这样就算是replace没有右孩子也没事replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;}return true;}}return false;
}
};
10.运行插入相等的值的二叉搜索树的最终代码
#include<iostream>
#include<algorithm>
using namespace std;
template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key), _left(nullptr), _right(nullptr){}
};
template<class K>
class BSTree
{typedef BSTNode<K> Node;
public://中序遍历函数的实现void _InOrder(Node* root){if (root == nullptr){return;}//递归左子树_InOrder(root->_left);//这个打印是看个人习惯,可以直接打印换行cout << root->_key << " ";//递归右子树_InOrder(root->_right);}//辅助函数void InOrder(){_InOrder(_root);cout << endl;}//二叉搜索树的插入(允许插入相等的值)
void Insert(const K& key, int)
{if (_root == nullptr){_root = new Node(key);return;}Node* parent = nullptr;Node* cur = _root;while (cur){if (key <= cur->_key){//如果小于或等于就往左走parent = cur;cur = cur->_left;}else{//如果大于就往右走parent = cur;cur = cur->_right;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}
}
//二叉搜索树的查找(允许插入相等的值)
int Find(const K& key,int)
{Node* cur = _root;int num = 0;while (cur){if (key == cur->_key){++num;cur = cur->_left;}else if(key > cur->_key){cur = cur->_right;}else{cur = cur->_left;}}return num;
}
//二叉搜索树的删除(允许插入相等的值)
bool Erase(const K& key, int)
{Node* cur = _root;Node* parent = nullptr;bool deleted = false;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{deleted = true;//开始删除//不要写错了,这代表有右孩子(右孩子也有可能为空)if (cur->_left == nullptr){//这个不是很容易理解//if (parent == nullptr)//替换成这样更好理解if (cur == _root){//直接改变根结点_root = cur->_right;}//cur是parent的左孩子else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;//如果parent为根结点if (parent == nullptr){cur = _root;}else{//如果先前为左孩子,则继续作为左孩子,只是原来的parent的左孩子被删了而已if (parent->_left == cur){cur = parent->_left;}//反之继续做右孩子,只是原来的parent的右孩子被删了而已else{cur = parent->_right;}}}//这代表有左孩子且左孩子不为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//cur是parent的左孩子if (cur->_left == cur){parent->_left == cur->_left;}else{parent->_right = cur->_left;}}delete cur;//如果parent为根结点if (parent == nullptr){cur = _root;}else{//如果先前为左孩子,则继续作为左孩子,只是原来的parent的左孩子被删了而已if (parent->_left == cur){cur = parent->_left;}//反之继续做右孩子,只是原来的parent的右孩子被删了而已else{cur = parent->_right;}}}else{Node* replace = cur->_right;Node* replaceParent = cur;while (replace->_left){replaceParent = replace;replace = replace->_left;}//记得包含头文件algorithmstd::swap(replace->_key, cur->_key);if (replaceParent->_left == replace){//这样就算是replace没有右孩子也没事replaceParent->_left = replace->_right;}else{replaceParent->_right = replace->_right;}delete replace;}}}return deleted;
}
};
11.总结
二叉搜索树难度相对于原来的各种知识要考虑的情况更多,后面的AVL树和红黑树难度更高,手动实现的更难,不过以后面试和笔试中要考到这些实现的注意事项,所以一定不要觉得这篇二叉搜索树难就不学了,后面的手动模拟实现map和set难度更上一层楼,所以任重而道远。
好了,这讲内容就到这里,下讲将讲解:C++进阶-set,喜欢的可以一键三连哦,下讲再见!!!