C++ 二叉搜索树(BST)的实现(非递归版本与递归版本)与应用

C++ 二叉搜索树的实现与应用

  • 一.二叉搜索树的特点
  • 二.我们要实现的大致框架
  • 三.Insert
  • 四.InOrder和Find
    • 1.InOrder
    • 2.Find
  • 五.Erase
  • 六.Find,Insert,Erase的递归版本
    • 1.FindR
    • 2.InsertR
    • 3.EraseR
  • 七.析构,拷贝构造,赋值运算符重载
    • 1.析构
    • 2.拷贝构造
    • 3.赋值运算重载
  • 八.Key模型完整代码
  • 九.二叉搜索树的应用
    • 1.Key模型
    • 2.Key-Value模型

二叉搜索树既可以实现为升序版本,也可以实现为降序版本
本文实现为升序版本

一.二叉搜索树的特点

二叉搜索树是一种特殊的二叉树
它的特点是:

1.左子树的所有节点均比根节点的值小
2.右子树的所有节点均比根节点的值大
3.左右子树都是二叉搜索树
4.中序遍历序列是有序的
5.一般二叉搜索树不允许有重复值

当然,二叉搜索树默认是升序的,不过也可以实现成降序的样子
只需要更改一下第1条和第2条即可,
第一条改为左子树的节点都要大于根节点
第二条改为右子树的节点都要小于根节点
此时实现出来的二叉搜索树就是降序的
在这里插入图片描述
例如:这个树就是一个二叉搜索树

二.我们要实现的大致框架

#pragma once
#include <iostream>
using namespace std;
//BST排升序:左孩子小于我,  右孩子大于我
//排降序:   左孩子大于我,  右孩子小于我//节点的结构体
template <class K>
struct BSTreeNode
{BSTreeNode<K>* _left = nullptr;BSTreeNode<K>* _right = nullptr;K _key;BSTreeNode(const K& key):_key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://非递归实现insert.find,erasebool Insert(const K& key);Node* Find(const K& key);bool Erase(const K& key);//析构函数  后续遍历析构~BSTree();//C++11新语法BSTree() = default;//强制生成默认构造//拷贝构造//先序遍历构造BSTree(const BSTree<K>& bst);//赋值运算符重载:现代版本BSTree<K>& operator=(BSTree<K> bst);void InOrder(){_InOrder(_root);}//递归实现insert.find,eraseNode* FindR(const K& key){return _FindR(_root,key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}private://拷贝构造函数的子函数Node* _Copy(const Node* root);//析构函数的子函数void _Destroy(Node*& root);//中序遍历的子函数void _InOrder(Node* root);//find的子函数Node* _FindR(Node* root, const K& key);//insert的子函数bool _InsertR(Node*& root, const K& key);//erase的子函数bool _EraseR(Node*& root, const K& key);//给根节点_root缺省值nullptrNode* _root = nullptr;
};

这是Key模型的版本,最后我们要修改一份Key-Value版本

template <class K>

这里模板给K的原因是:贴合K模型而已,所以没有用T
这里的R : recursion(递归的英文)

//给根节点_root缺省值nullptr
Node* _root = nullptr;

这里直接给根节点_root缺省值nullptr了,编译器默认生成的构造函数就会使用这个缺省值
这里补充一点:

//C++11新语法:给default这个关键字增加了一个含义
BSTree() = default;//强制生成默认构造

三.Insert

学习了二叉搜索树的特点之后,我们来看如何插入一个值
在这里插入图片描述
注意:
1.在遍历查找要插入的位置时一定要记录父节点,否则无法插入
2.最后插入的时候要判断该值与父节点的大小关系,这样才能知道要插入到左侧还是右侧

因此我们就可以写出这样的代码

插入成功,返回true
插入失败(说明插入了重复值),返回false
bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = _root;//记录父亲,因为要能够插入while (cur){//要插入的值小于父亲,往左找if (cur->_key > key){parent = cur;cur = cur->_left;}//要插入的值大于父亲,往右找else if (cur->_key < key){parent = cur;cur = cur->_right;}//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回falseelse{return false;}}//此时cur为空,说明找到了空位置 在此位置插入valuecur = new Node(key);//要插入的元素小于父亲,插入到左侧if (parent->_key > key){parent->_left = cur;}//要插入的元素大于父亲,插入到右侧else{parent->_right = cur;}//插入成功return true;
}

四.InOrder和Find

1.InOrder

关于InOrder中序遍历跟普通二叉树的中序遍历是一模一样的
只不过因为要用递归去实现,而且_root是私有变量不能让外界访问到,因此封装了一个子函数,让子函数去递归完成任务,主函数可以被外界调用到,子函数无需提供给外界
同理,后面的Insert,Erase,Find的递归版本都是封装了一个子函数,跟InOrder这方面的思路一样

void InOrder()
{_InOrder(_root);cout << endl;
}
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);
}

2.Find

学习了Insert之后,Find对我们来说就很简单了
要查找一个值key
1.key小于当前节点的值,往左找
2.key大于当前节点的值,往右找
3.key等于当前节点的值,找到了,返回该节点
4.要查找的当前节点为空节点,返回nullptr,代表查找失败

Node* 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 cur;}}//此时cur为空说明没有找到return nullptr;
}

此时我们就可以开始玩这个二叉搜索树了
在这里插入图片描述
可以看出,中序遍历的确是有序的

五.Erase

前面的insert和find都比较简单,接下来的erase就比较复杂了
erase分为4种情况:
对于要删除的节点
1.该节点没有左孩子,也没有右孩子
在这里插入图片描述
不过这里最后删除的时候是不对的,因为14依旧指向13,而13已经被释放了,所以14的_left指针就成为了野指针,怎么办呢?
此时只需要先让该节点的父亲(也就是14)指向空,
然后就可以放心地删除13了
正确的版本:
在这里插入图片描述
2.该节点没有左孩子,但是有右孩子
此时只需要把该节点的右孩子托付给父亲即可
在这里插入图片描述
3.该节点有左孩子,不过没有右孩子
此时只需要把该节点的左孩子托付给父亲即可
其实第一类可以归为第二类或者第三类
在这里插入图片描述
4.该节点既有左孩子,又有右孩子
在这里插入图片描述
其实这里的1就是整棵树当中小于3这个值的最大值
4就是整棵树当中大于3这个值的最小值
他们都可以来代替3这个值

1其实就是要删除的节点的左子树的最大值(最大值就是最右侧节点)
4其实就是要删除的节点的右子树的最小值(最大值就是最左侧节点)

而且1和4都有一个特点:最多只有一个孩子
此时删除1和4就可以借助于第2种或第3种方案了

我们今天按照寻找右子树的最小值的方式来做

注意:之后删除3的操作不能使用递归,因为交换后就不是二叉搜索树了,就无法保证能够找到那个值了

不过上述的讨论当中我们讨论的都是该节点有父亲的情况
都没有讨论下面的这种情况:
5.我要删除的是根节点呢?
(1).根节点没有左孩子也没有右孩子

Node* tmp = _root;
_root=nullptr;
delete tmp;

在这里插入图片描述
(2).根节点只有1个孩子
因为我们知道:一个二叉搜索树的左子树和右子树都是二叉搜索树
比方说根节点只有左孩子,没有右孩子
此时只需要让根节点等于左子树的根节点(也就是根节点的左孩子)即可
在这里插入图片描述
删除根节点之前:
在这里插入图片描述
删除根节点之后:
在这里插入图片描述
可见,这么做的话,删除之后的确也还是二叉搜索树
同理,节点只有右孩子,没有左孩子的时候
只需要让根节点等于右子树的根节点(也就是根节点的右孩子)即可

同理,第一种情况也可以归为第二种情况
(3).根节点有2个孩子
在这里插入图片描述
删除之前:
在这里插入图片描述
删除之后:
在这里插入图片描述
在这里插入图片描述
不过这里也分为两种情况
1.因为查找的是右子树的最左侧节点
也就是一路往左查找,因此最后的时候只需要让我的右孩子成为父亲的左孩子即可

2.不过如果没有一路查找,直接找到了的话
也就是说此时我是父亲的右孩子,那么就要让我的右孩子成为父亲的右孩子了

上面演示的那种情况就属于第二种情况
因此,我们就可以写出这样的代码
里面的注释非常详细,大家如果还不是特别理解的话,
可以对照着边走读代码边画图来更好地理解

//删除成功,返回true
//删除失败,说明没有这个元素,返回false
bool Erase(const K& key)
{//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类//2.有右孩子,没有左孩子//3.有左孩子,没有右孩子//4.有左孩子,也有右孩子Node* cur = _root;Node* parent = cur;//父亲while (cur){//往左找if (cur->_key > key){parent = cur;cur = cur->_left;}//往右找else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到了else{//1.有右孩子,没有左孩子//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)if (cur->_left == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_right;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的右孩子成为父亲的左孩子parent->_left = cur->_right;delete cur;}//说明我是父亲的右孩子else{//就让我的右孩子成为父亲的右孩子parent->_right = cur->_right;delete cur;}}//2.有左孩子,没有右孩子else if (cur->_right == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_left;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的左孩子成为父亲的左孩子parent->_left = cur->_left;delete cur;}//说明我是父亲的右孩子else{//就让我的左孩子成为父亲的右孩子parent->_right = cur->_left;delete cur;}}//3.有左孩子,也有右孩子//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了else{parent = cur;Node* MinOfRight = cur->_right;while (MinOfRight->_left){parent = MinOfRight;MinOfRight = MinOfRight->_left;}//开始交换swap(cur->_key, MinOfRight->_key);//然后删除MinOfRight//1.的确向下查找了//此时MinOfRight就是parent的左孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了if (parent->_left == MinOfRight){parent->_left = MinOfRight->_right;delete MinOfRight;}//2.没有继续往下查找//此时MinOfRight就是parent的右孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了else{parent->_right = MinOfRight->_right;delete MinOfRight;}}//删除成功return true;}}//此时cur为空说明没有找到return false;
}

六.Find,Insert,Erase的递归版本

1.FindR

Find的递归版本就很简单了:
假设要查找的值是Key
如果当前节点的值==key:查到了,返回当前节点即可
如果当前节点的值>key:说明当前节点值太大,往左找
如果当前节点的值<key:说明当前节点值太小,往右找

Node* FindR(const K& key)
{return _FindR(_root,key);
}
Node* _FindR(Node* root, const K& key)
{if (root == nullptr){return nullptr;}if (root->_key > key){return _FindR(root->_left, key);}else if(root->_key < key){return _FindR(root->_right, key);}else{return root;}
}

2.InsertR

如果当前节点是空节点:说明找到了空位置,插入即可
如果当前节点的值>key:说明当前节点值太大,往左找插入位置
如果当前节点的值<key:说明当前节点值太小,往右找插入位置
如果当前节点的值==key:说明重复了,返回false,不能插入重复元素

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 (root->_key > key){return _InsertR(root->_left, key);}else if(root->_key < key){return _InsertR(root->_right, key);}else{return false;}
}

这里特别巧妙的一点在于:只要加上引用
那么就可以不用传递父节点了
因为root就是上一个节点的左孩子或者右孩子的别名,改变root会影响到上一个节点的左孩子或者右孩子

这里引用作为参数的价值就显得格外巧妙了

3.EraseR

这里是递归版本的erase,
而且要删除的节点跟MinOfRight交换之后,右子树是一个二叉搜索树
因此后面删除MinOfRight的时候可以复用,直接在右子树上面删除MinOfRight即可
而且对于删除根节点也是如此

这里依旧使用引用作为参数,它的巧妙之处在于修改指向时特别方便了,无需传入父亲节点

bool EraseR(const K& key)
{return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{if (root == nullptr){return false;}//1.往左找,在左子树里面删除keyif (root->_key > key){return _EraseR(root->_left, key);}//2.往右找,在右子树里面删除keyelse if (root->_key < key){return _EraseR(root->_right, key);}// 当前的根节点else{//root不仅仅是root,root是父亲的孩子的别名//因此只需要改变root就可以改变父亲的孩子了if (root->_left == nullptr){//不要忘了保存rootNode* del = root;root = root->_right;//这里不是迭代,而是修改指向,是把我的右孩子托付给父亲delete del;return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{Node* MinOfRight = root->_right;while (MinOfRight->_left){MinOfRight = MinOfRight->_left;}swap(root->_key, MinOfRight->_key);//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST//所以此时递归删除root->_right上的key值即可//而且也适用于直接删除根节点的情况_EraseR(root->_right, key);}}return true;
}

七.析构,拷贝构造,赋值运算符重载

1.析构

跟二叉树的销毁一样,后序遍历销毁
依旧是采用递归版本

//析构函数  后续遍历析构
~BSTree()
{_Destroy(_root);
}
void _Destroy(Node*& root)
{if (root == nullptr) return;_Destroy(root->_left);_Destroy(root->_right);delete root;root = nullptr;
}

2.拷贝构造

先序遍历构造
先构造根节点,然后递归构造左子树和右子树
最后返回根节点

//拷贝构造
//先序遍历构造
BSTree(const BSTree<K>& bst)
{_root = _Copy(bst._root);
}
Node* _Copy(const 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;
}

3.赋值运算重载

实现了拷贝构造之后就可以
直接现代写法了

//赋值运算符重载
BSTree<K>& operator=(BSTree<K> bst)
{std::swap(_root, bst._root);return *this;
}

八.Key模型完整代码

template <class K>
struct BSTreeNode
{BSTreeNode<K>* _left = nullptr;BSTreeNode<K>* _right = nullptr;K _key;BSTreeNode(const K& key):_key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://非递归实现insert.find,erasebool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = _root;//记录父亲,因为要能够插入while (cur){//要插入的值小于父亲,插入到左子树当中if (cur->_key > key){parent = cur;cur = cur->_left;}//要插入的的值大于父亲,插入到右子树当中else if (cur->_key < key){parent = cur;cur = cur->_right;}//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回falseelse{return false;}}//此时cur为空,在此位置插入valuecur = new Node(key);//要插入的元素小于父亲,插入到左子树当中if (parent->_key > key){parent->_left = cur;}//要插入的元素大于父亲,插入到右子树当中else{parent->_right = cur;}//插入成功return true;}Node* 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 cur;}}//此时cur为空说明没有找到return nullptr;}bool Erase(const K& key){//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类//2.有右孩子,没有左孩子//3.有左孩子,没有右孩子//4.有左孩子,也有右孩子Node* cur = _root;Node* parent = cur;//父亲while (cur){//往左找if (cur->_key > key){parent = cur;cur = cur->_left;}//往右找else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到了else{//1.有右孩子,没有左孩子//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)if (cur->_left == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_right;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的右孩子成为父亲的左孩子parent->_left = cur->_right;delete cur;}//说明我是父亲的右孩子else{//就让我的右孩子成为父亲的右孩子parent->_right = cur->_right;delete cur;}}//2.有左孩子,没有右孩子else if (cur->_right == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_left;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的左孩子成为父亲的左孩子parent->_left = cur->_left;delete cur;}//说明我是父亲的右孩子else{//就让我的左孩子成为父亲的右孩子parent->_right = cur->_left;delete cur;}}//3.有左孩子,也有右孩子//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了else{parent = cur;Node* MinOfRight = cur->_right;while (MinOfRight->_left){parent = MinOfRight;MinOfRight = MinOfRight->_left;}//开始交换swap(cur->_key, MinOfRight->_key);//然后删除MinOfRight//1.的确向下查找了//此时MinOfRight就是parent的左孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了if (parent->_left == MinOfRight){parent->_left = MinOfRight->_right;delete MinOfRight;}//2.没有继续往下查找//此时MinOfRight就是parent的右孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了else{parent->_right = MinOfRight->_right;delete MinOfRight;}}//删除成功return true;}}//此时cur为空说明没有找到return false;}//析构函数  后续遍历析构~BSTree(){_Destroy(_root);}//C++11新语法BSTree() = default;//强制生成默认构造//拷贝构造//先序遍历构造BSTree(const BSTree<K>& bst){_root = _Copy(bst._root);}//赋值运算符重载BSTree<K>& operator=(BSTree<K> bst){std::swap(_root, bst._root);return *this;}void InOrder(){_InOrder(_root);cout << endl;}//递归实现insert.find,eraseNode* FindR(const K& key){return _FindR(_root,key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool EraseR(const K& key){return _EraseR(_root, key);}
private:Node* _Copy(const 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;}void _Destroy(Node*& root){if (root == nullptr) return;_Destroy(root->_left);_Destroy(root->_right);delete root;root = nullptr;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _FindR(Node* root, const K& key){if (root == nullptr){return nullptr;}if (root->_key > key){return _FindR(root->_left, key);}else if(root->_key < key){return _FindR(root->_right, key);}else{return root;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}else if (root->_key > key){return _InsertR(root->_left, key);}else if(root->_key < key){return _InsertR(root->_right, key);}else{return false;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}//1.往左找if (root->_key > key){return _EraseR(root->_left, key);}//2.往右找else if (root->_key < key){return _EraseR(root->_right, key);}// 删除else{//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可if (root->_left == nullptr){Node* del = root;root = root->_right;//这里不是迭代,而是修改指向,托孤delete del;return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{Node* MinOfRight = root->_right;while (MinOfRight->_left){MinOfRight = MinOfRight->_left;}swap(root->_key, MinOfRight->_key);//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST//所以此时递归删除root->_right上的key值即可//而且对于直接删除_root也没有任何影响_EraseR(root->_right, key);}}return true;}Node* _root = nullptr;
};

九.二叉搜索树的应用

1.Key模型

在这里插入图片描述

2.Key-Value模型

在这里插入图片描述
下面我们把刚才Key模型的代码改为Key-Value模型
只需要改一下:
1.BSTreeNode节点
2.insert
3.InOrder的打印即可
其他地方都不需要修改

namespace kv
{template <class K,class V>struct {BSTreeNode<K,V>* _left = nullptr;BSTreeNode<K,V>* _right = nullptr;K _key;V _value;BSTreeNode(const K& key,const V& value):_key(key),_value(value){}};template<class K,class V>class BSTree{typedef BSTreeNode<K,V> Node;public://非递归实现insert.find,erasebool Insert(const K& key,const V& value){if (_root == nullptr){_root = new Node(key,value);return true;}Node* cur = _root;Node* parent = _root;//记录父亲,因为要能够插入while (cur){//要插入的值小于父亲,插入到左子树当中if (cur->_key > key){parent = cur;cur = cur->_left;}//要插入的的值大于父亲,插入到右子树当中else if (cur->_key < key){parent = cur;cur = cur->_right;}//出现了重复元素,BST搜索二叉树不允许出现重复值,因此不允许插入,返回falseelse{return false;}}//此时cur为空,在此位置插入valuecur = new Node(key,value);//要插入的元素小于父亲,插入到左子树当中if (parent->_key > key){parent->_left = cur;}//要插入的元素大于父亲,插入到右子树当中else{parent->_right = cur;}//插入成功return true;}Node* 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 cur;}}//此时cur为空说明没有找到return nullptr;}bool Erase(const K& key){//1.没有左孩子,没有右孩子 可以归为2,3中的任意一类//2.有右孩子,没有左孩子//3.有左孩子,没有右孩子//4.有左孩子,也有右孩子Node* cur = _root;Node* parent = cur;//父亲while (cur){//往左找if (cur->_key > key){parent = cur;cur = cur->_left;}//往右找else if (cur->_key < key){parent = cur;cur = cur->_right;}//找到了else{//1.有右孩子,没有左孩子//此时只需要让他的右孩子代替它的位置即可(也就是把自己的右孩子给父亲,然后删除自己即可)if (cur->_left == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_right;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的右孩子成为父亲的左孩子parent->_left = cur->_right;delete cur;}//说明我是父亲的右孩子else{//就让我的右孩子成为父亲的右孩子parent->_right = cur->_right;delete cur;}}//2.有左孩子,没有右孩子else if (cur->_right == nullptr){//要删除的是_root,且_root没有左孩子//那么让右孩子变成root即可if (cur == _root){_root = cur->_left;delete cur;}//说明我是父亲的左孩子if (cur == parent->_left){//就让我的左孩子成为父亲的左孩子parent->_left = cur->_left;delete cur;}//说明我是父亲的右孩子else{//就让我的左孩子成为父亲的右孩子parent->_right = cur->_left;delete cur;}}//3.有左孩子,也有右孩子//我既可以让左子树的最大值替代我,也可以让右子树的最小值替代我//这里就找右子树的最小值吧,右子树的最小值就是右子树的最左侧节点//找到右子树中的最小值,将他的值跟我交换,然后删除刚才那个节点//注意:"删除刚才那个节点"的操作不能使用递归,因为交换后就不是BST了,就无法保证能够找到那个值了else{parent = cur;Node* MinOfRight = cur->_right;while (MinOfRight->_left){parent = MinOfRight;MinOfRight = MinOfRight->_left;}//开始交换swap(cur->_key, MinOfRight->_key);//然后删除MinOfRight//1.的确向下查找了//此时MinOfRight就是parent的左孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的左孩子,然后就可以删除了if (parent->_left == MinOfRight){parent->_left = MinOfRight->_right;delete MinOfRight;}//2.没有继续往下查找//此时MinOfRight就是parent的右孩子//并且此时MinOfRight没有左孩子,那么就可以直接把MinOfRight的右孩子给parent当做它的右孩子,然后就可以删除了else{parent->_right = MinOfRight->_right;delete MinOfRight;}}//删除成功return true;}}//此时cur为空说明没有找到return false;}//析构函数  后续遍历析构~BSTree(){_Destroy(_root);}//C++11新语法BSTree() = default;//强制生成默认构造//拷贝构造//先序遍历构造BSTree(const BSTree<K,V>& bst){_root = _Copy(bst._root);}//赋值运算符重载BSTree<K,V>& operator=(BSTree<K,V> bst){std::swap(_root, bst._root);return *this;}void InOrder(){_InOrder(_root);cout << endl;}//递归实现insert.find,eraseNode* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key,const V& value){return _InsertR(_root, key,value);}bool EraseR(const K& key){return _EraseR(_root, key);}private:Node* _Copy(const 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;}void _Destroy(Node*& root){if (root == nullptr) return;_Destroy(root->_left);_Destroy(root->_right);delete root;root = nullptr;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << " ";_InOrder(root->_right);}Node* _FindR(Node* root, const K& key){if (root == nullptr){return nullptr;}if (root->_key > key){return _FindR(root->_left, key);}else if (root->_key < key){return _FindR(root->_right, key);}else{return root;}}bool _InsertR(Node*& root, const K& key,const V& value){if (root == nullptr){root = new Node(key,value);return true;}else if (root->_key > key){return _InsertR(root->_left, key);}else if (root->_key < key){return _InsertR(root->_right, key);}else{return false;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}//1.往左找if (root->_key > key){return _EraseR(root->_left, key);}//2.往右找else if (root->_key < key){return _EraseR(root->_right, key);}// 删除else{//root不仅仅是root,root是父亲的孩子的别名,让root成为root的右孩子即可if (root->_left == nullptr){Node* del = root;root = root->_right;//这里不是迭代,而是修改指向,托孤delete del;return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{Node* MinOfRight = root->_right;while (MinOfRight->_left){MinOfRight = MinOfRight->_left;}swap(root->_key, MinOfRight->_key);//注意:现在是递归版本,参数可以传入节点,此时这棵树不是BST,但是root的右子树是BST//所以此时递归删除root->_right上的key值即可//而且对于直接删除_root也没有任何影响_EraseR(root->_right, key);}}return true;}Node* _root = nullptr;};
}

下面我们来测试一下
一个是统计单词出现的次数
一个是英汉互译的词典

void TestBSTree()
{string strs[] = { "apple","Banana","Grape","Mango","apple","Banana" ,"apple","Mango" ,"Mango" ,"Mango" ,"Mango" };// 统计单词出现的次数kv::BSTree<string, int> countTree;for (auto str : strs){auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();//英汉互译的词典kv::BSTree<string, string> dict;dict.Insert("insert", "插入");dict.Insert("erase", "删除");dict.Insert("BST", "二叉搜索树");dict.Insert("KV", "key-value模型");string str;while (cin >> str){auto ret = dict.Find(str);if (ret){cout << str << ":" << ret->_value << endl;}else{cout << "单词拼写错误" << endl;}}
}

在这里插入图片描述

以上就是C++ 二叉搜索树(BST)的实现(非递归版本与递归版本)与应用的全部内容,希望能对大家有所帮助!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/228049.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2020年第九届数学建模国际赛小美赛D题石头剪刀游戏与合作解题全过程文档及程序

2020年第九届数学建模国际赛小美赛 D题 石头剪刀游戏与合作 原题再现&#xff1a; 小时候你可能至少玩过几次石头剪刀游戏。在这个游戏中&#xff0c;你几乎有三个选择&#xff0c;每一个都有一个项目要打败&#xff0c;一个项目输给。石头打败剪刀&#xff0c;剪刀剪纸和布覆…

yasaf永安视安防监控恢复案例

永安视(yasaf)一个小到连官网都没有的安防厂商&#xff0c;是国内众多品牌中的一个&#xff0c;可以说是小十八线&#xff0c;让人敬佩的是这么一个小厂也自行写了嵌入式方案&#xff0c;下边我们来看看这个恢复案例。 故障存储: WD500G 故障现象: 此安防设备在使用过程中被…

python/c++ Leetcode题解——1.两数之和

目录 方法1:枚举法 思路 Code 方法2:哈希表 思路 Code 方法1:枚举法 思路 最容易想到的方法是枚举数组中的每一个数 x&#xff0c;寻找数组中是否存在 target - x。 当我们使用遍历整个数组的方式寻找 target - x 时&#xff0c;需要注意到每一个位于 x 之前的元素都已…

【机器学习】应用KNN实现鸢尾花种类预测

目录 前言 一、K最近邻&#xff08;KNN&#xff09;介绍 二、鸢尾花数据集介绍 三、鸢尾花数据集可视化 四、鸢尾花数据分析 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Fil…

07-抽象工厂

意图 提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 适用性 在以下的情况可以选择使用抽象工厂模式&#xff1a; 一个系统要独立于它的产品的创建、组合和表示。一个系统要由多个产品系列中的一个来配置。要强调一系列相关的产品对象的…

Spring MVC 中的常用注解和用法

目录 一、什么是 Spring MVC 二、MVC定义 三、简述 SpringMVC 起到的作用有哪些? 四、注解 五、请求转发或请求重定向 一、什么是 Spring MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架&#xff0c;从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web…

移动端Vant中的Calendar日历增加显示农历(节日、节气)功能

核心&#xff1a; 使用 js-calendar-converter 库实现 npm地址&#xff1a;js-calendar-converter 内部使用原生calendar.js&#xff0c; 中国农历&#xff08;阴阳历&#xff09;和西元阳历即公历互转JavaScript库&#xff0c;具体实现感兴趣的可自行查看其实现源码。 原日…

唱作歌手朱卫明的粤语版《兄弟情》:一曲深情唱尽人间真挚情感

朱卫明的粤语版《兄弟情》&#xff1a;一曲深情唱尽人间真挚情感 在音乐的广阔天地里&#xff0c;每种语言都有其独特的韵味和魅力。对于唱作音乐人朱卫明来说&#xff0c;他用普通话演唱的歌曲已经深入人心&#xff0c;但当他将《兄弟情》这首歌曲以粤语演绎时&#xff0c;更…

笔记 - 现代嵌入式芯片封装识读

0.引用&#xff1a; 配图、资料并非一处采集&#xff0c;我不太容易找到图片的原始链接。这里的图片仅作示例&#xff0c;无商业用途。如果涉及侵权&#xff0c;请随时联系。谢谢&#xff01; PCB封装欣赏了解之旅&#xff08;下篇&#xff09;—— 常用集成电路_ufqfpn封装…

【C++】STL 容器 - string 字符串操作 ⑤ ( string 字符串查找 | find 函数查找字符串 | rfind 函数查找字符串 )

文章目录 一、string 字符查找 - find 函数查找字符串1、string 类 find 函数原型说明2、代码示例 - 字符串查找3、代码示例 - 统计字符串子串 二、string 字符查找 - rfind 函数查找字符串1、string 类 rfind 函数原型说明2、代码示例 - rfind 字符串查找 一、string 字符查找…

注册与回调

C 再谈谈注册(本质是建立映射)与回调 在之前的博文中&#xff0c; 我们探讨过映射的重要作用&#xff0c; 请直接看&#xff1a;http://blog.csdn.net/stpeace/article/details/39452203, 在那篇文章中&#xff0c; 我们是用STL中的map来做的&#xff0c; map建立的是key-value…

rk3568 RGMII KSZ8795 MAC TO MAC

RK3568与KSZ8795交换机芯片连接&#xff0c;直接MAC TO MAC方式&#xff0c;这样一下就扩展会4路网口&#xff0c;应该场合比较多&#xff0c;移植过程如下&#xff1a; 参考《Rockchip_Developer_Guide_Linux_MAC_TO_MAC_CN.pdf》 《rockchip RGMIImv88e6390 管理型交换机功…

【Spring】之Ioc和Aop快速了解

这里写目录标题 1.Spring框架是什么&#xff1f;简介&#xff1a;总结&#xff1a;Spring框架&#xff0c;可以理解为是一个管理者&#xff1a;管理整个分层架构&#xff08;MVC&#xff09;中的每一个对象&#xff1b;&#xff08;每一个对象称之为bean&#xff09; 2.Spring框…

太空旅行:计算机技术的崭新航程

太空旅行&#xff1a;计算机技术的崭新航程 一、引言 自古以来&#xff0c;人类就对浩渺的宇宙充满了无尽的好奇和渴望。随着科技的飞速发展&#xff0c;太空旅行已经从科幻小说中的构想变为现实。在这个过程中&#xff0c;计算机技术起到了不可或缺的作用。从阿波罗时代的初…

​FL Studio2024最新版本好不好用?有哪些新功能

FL Studio2024版是一款在国内非常受欢迎的多功能音频处理软件&#xff0c;我们可以通过这款软件来对多种不同格式的音频文件来进行编辑处理。而且FL Studio 2024版还为用户们准备了超多的音乐乐器伴奏&#xff0c;我们可以直接一键调取自己需要的音调。 FL Studio 2024版不仅拥…

HarmonyOS4.0从零开始的开发教程15HTTP数据请求

HarmonyOS&#xff08;十三&#xff09;HTTP数据请求 1 概述 日常生活中我们使用应用程序看新闻、发送消息等&#xff0c;都需要连接到互联网&#xff0c;从服务端获取数据。例如&#xff0c;新闻应用可以从新闻服务器中获取最新的热点新闻&#xff0c;从而给用户打造更加丰富…

MySQL进阶2 - 索引

MySQL进阶1 - 索引 1. 索引概述2. 索引结构2.1 二叉树2.2 B-Tree(多路平衡查找树)2.3 BTree2.4 Hash 3. 索引分类4. 索引语法5. SQL性能分析5.1 SQL执行频率5.2 慢查询日志5.3 profile5.4 explain执行计划5.3.1 EXPLAIN执行计划各字段含义&#xff1a; 6. 索引使…

SpringBoot+FastJson 优雅的过滤 Response Body

Spring 源码系列 1、Spring 学习之扩展点总结之后置处理器&#xff08;一&#xff09; 2、Spring 学习之扩展点总结之后置处理器&#xff08;二&#xff09; 3、Spring 学习之扩展点总结之自定义事件&#xff08;三&#xff09; 4、Spring 学习之扩展点总结之内置事件&#xf…

手把手教你Linux查找Java的安装目录并设置环境变量以及Linux下执行javac未找到命令的保姆级教学

查找Java的安装目录 输入 java -version&#xff0c;查看是否成功安装Java 输入 which java&#xff0c;查看Java的执行路径 输入 ls -lrt /usr/bin/java 输入 ls -lrt /etc/alternatives/java&#xff0c;/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-0.el7_7.x86_64 就是J…

自动化测试(终章)webdriver的常用api(2)以及新的开始

目录 多层框架/窗口定位 多层框架的定位 frame是什么&#xff1f; 多层窗口定位 层级定位 使用 XPath 进行层级定位&#xff1a; 使用 CSS 选择器进行层级定位&#xff1a; 下拉框处理 alert、confirm、prompt 的处理 Alert 弹窗&#xff1a; Confirm 弹窗&#xff…