当前位置: 首页 > news >正文

【C++】二叉搜索树

一、二叉搜索树的概念

二叉搜索树又称搜索二叉树,它或者是一棵空树,或者是具有以下性质的⼆叉树:

  • 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值。

  • 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值。

  • 它的左右子树也分别为⼆叉搜索树。

⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义。

比如:map / set / multimap / multiset 系列容器底层就是⼆叉搜索树,其中map/set不支持插入相等
值,multimap/multiset支持插入相等值。

二、二叉搜索树的性能分析

在二叉搜索树中,如果想要搜查到某一个数时,最多要找高度次。

下面分两种情况:

最优情况下,二叉搜索树为完全二叉树(或者接近完全⼆叉树),其时间复杂度为:O(\log_{2}N)
最差情况下,二叉搜索树退化为单支树(或者类似单支),其时间复杂度为:O(N)

那么像O(N)这样的效率显然是无法满足我们需求的,我们必须对最坏情况下的二叉搜索树进行控制,所以就有了⼆叉搜索树的变形,即平衡⼆叉搜索树(可以降低高度),平衡搜索树又衍生出了AVL树、红黑树、B树系列(B树、B+树、B*树)等,这几种树都可以满足查找,还有另外一种结构-哈希表,也可以进行查找。

大家先前应该学习过二分查找,这种查找效率也是很高的,它的时间复杂度是:O(\log_{2}N),但它有局限性:

  1. 需要存储在支持下标随机访问的结构中,并且有序。
  2. 插入和删除数据效率很低,因为存储在下标随机访问的结构中,插入和删除数据一般需要挪动数据。

 三、二叉搜索树的具体实现

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、二叉搜索树的插入

 ⼆叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,我们通常会将这两种情况分开实现,我们下面的实现以不支持插入相等的值为例进行操作,那么二叉树就遵循左子树上所有结点的值都小于根结点的值,右子树上所有结点的值都大于根结点的值。

插入的具体过程:

  1. 树为空:则直接新增结点,赋值给root指针。
  2. 树不为空:按⼆叉搜索树性质,插入值比当前结点大往右走,插入值比当前结点小往左走,找到空位置,插入新结点。
  3. 如果支持插入相等的值,插入值跟当前结点相等的值可以往右走,也可以往左走,找到空位置,插入新结点。(要注意的是要保持逻辑⼀致性,插入相等的值不要一会往右走,一会往左走)

代码实现:

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;
//}

九、结语

本篇到这里就结束了,主要讲了二叉搜索树的基本概念以及实现逻辑,希望对大家有所帮助,祝生活愉快,我们下篇再见! 


http://www.mrgr.cn/news/40604.html

相关文章:

  • 浅谈UBS—TTL
  • Express内置的中间件(express.json和express.urlencoded)格式的请求体数据
  • 【SpringCloud】 统⼀服务⼊⼝-Gateway
  • CTMO时代下的营销新力量:2+1链动模式AI智能名片商城小程序
  • 常用快捷键整理
  • 文心一言 VS 讯飞星火 VS chatgpt (359)-- 算法导论24.3 1题
  • QT combox 前缀匹配
  • linux如何与网络时间对齐(雪花算法ID重复)
  • 使用PyTorch实现自然语言处理:从基础到实践
  • 【高频SQL基础50题】16-20
  • 【空中计算】Over-the-air Computing in OFDM Systems
  • sadTalker本地编译
  • 【GESP】C++一级练习BCQM3020,输入-计算-输出
  • 计算两点结构的斜率
  • 深度学习--------------------长短期记忆网络(LSTM)
  • C++11智能智能指针解析
  • 8G 显存玩转书生大模型 Demo
  • 原来还有【快速排序】 qsort() 函数
  • 迪杰斯特拉算法 Dijkstra‘s Algorithm 详解
  • 音频内容创作难吗?5分钟了解NotebookLM自动生成播客:让内容创作变得如此简单