
◆博主名称:少司府
欢迎来到少司府的博客☆*: .。. o(≧▽≦)o .。.:*☆
⭐数据结构系列个人专栏:
⭐水滴石穿非一日,功不唐捐终可期
目录
[1.1 概念](#1.1 概念)
[1.2 性能分析](#1.2 性能分析)
[2.1 插入](#2.1 插入)
[2.2 中序遍历](#2.2 中序遍历)
[2.3 查找](#2.3 查找)
[2.4 删除](#2.4 删除)
[7.1 key搜索场景](#7.1 key搜索场景)
[7.2 key/value搜索场景](#7.2 key/value搜索场景)
[7.3 代码实现](#7.3 代码实现)
1、二叉搜索树的概念及性能
1.1 概念
二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1)、若左子树不为空,则左子树 所有节点的值小于等于根节点
2)、若右子树不为空,则右子树 所有节点的值大于等于根节点
3)、它的左右子树也是二叉搜索树
4)、二叉搜索树可以支持插入相等的值,也可以不支持,具体看场景定义。

1.2 性能分析
1)、理想情况下,二叉搜索树类似于完全二叉树,高度是:log2 N
2)、最差情况下,二叉搜索树退化为单叉树,高度是:N
因此,二叉搜索树查找数据的时间复杂度是:O(N)

2、二叉搜索树的实现
在实现二叉搜索树诸多功能之前,我们先来写一下基本框架:
cpp
template<class K>
struct BSTNode
{
K _key;
BSTNode<K>* _left;
BSTNode<K>* _right;
BSTNode(const K& key)
:_key(key)
, _left(nullptr)
, _right(nullptr)
{ }
};
首先需要实现的是每一个树节点的定义,和普通的二叉树没有区别。
cpp
// Binary Search Tree
// Key 关键字
template<class K>
class BSTree
{
//typedef BSTNode<K> node;
using Node = BSTNode<K>;
public:
private:
Node* _root = nullptr;
};
如图,C++11支持用using来重命名,具体作用和 typedef 类似。
2.1 插入
插入的具体过程如下:
1)、树为空,直接 new 一个节点
2)、不为空,根据搜索二叉树的定义,比根节点大的插右边,小的插左边

cpp
bool Insert(const K& key)
{
if (_root == nullptr)
{
_root = new Node(key);
return true;
}
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (cur->_key < key)
cur = cur->_right;
else if (cur->_key > key)
cur = cur->_left;
else
return false;
}
cur = new Node(key);
if (parent->_key < key)
parent->_right = cur;
else
parent->_left = cur;
return true;
}
如图,用两个指针,cur 指向插入的位置,parent指向父亲节点。
2.2 中序遍历
cpp
public:
void Inorder()
{
_Inorder(_root);
cout << endl;
}
private:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
中序遍历逻辑和之前一样,需要注意的是:C++中private修饰的成员无法在类外进行调用,但是我们可以通过递归实现功能。相当于多封装一层,这样就不用显示传根节点指针。
当然也可以通过实现一个 GetRoot() 函数来完成该功能。
2.3 查找
查找的逻辑:
1)、从根节点开始查找val,val 比节点的值大往右边找,比节点的值小往左边找
2)、最多查找高度次,走到空没找到,则不存在val
3)、如果允许相等的值存在,则查找的是中序的第一个val
cpp
bool Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return true;
}
}
return false;
}
2.4 删除
删除的逻辑:
首先要查找元素是否在二叉搜索树中 ,如果不在,直接返回false。如果在,则需要分为以下几种情况:
1)、要删除的节点N左右孩子为空(叶子节点 ):把该节点的父亲对应的孩子指针置空,并删除该节点N
2)、要删除的节点N左孩子空,右孩子不为空:该节点的父亲节点对应孩子指针指向该节点N的右孩子,并删除该节点
3)、要删除的节点N右孩子空,左孩子不为空:该节点的父亲节点对应孩子指针指向该节点N的左孩子,并删除该节点
4)、要删除的节点N左右孩子均不为空:我们这里用替代法----找到N的左子树最大值(左子树最右节点)或者右子树最小值(右子树最左节点)val,找到的最小(最大)节点记为R,把N节点的值key替换为val,然后删除R(满足二叉搜索树的规则)



cpp
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
parent = cur;
if (cur->_key < key)
cur = cur->_right;
else if (cur->_key > key)
cur = cur->_left;
else
{
// 删除
// 左为空
if (cur->_left == nullptr)
{
if (cur == _root)
_root = _root->_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 = _root->_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;
}
cur->_key = replace->_key;
if (replaceParent->_left == replace)
replaceParent->_left = replace->_right;
else
replaceParent->_right = replace->_right;
delete replace;
}
return true;
}
}
return false;
}
三、二叉搜索树key和key/value使用场景
7.1 key搜索场景
只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值,搜索场景只需要判断 key在不在。key的搜索场景实现的⼆叉树搜索树⽀持增删查,但是不⽀持修改,修改key破坏搜索树结构了。
7.2 key/value搜索场景
每⼀个关键码key,都有与之对应的值value,value可以任意类型对象。树的结构中(结点)除了需要存 储key还要存储对应的value,增/删/查还是以key为关键字⾛⼆叉搜索树的规则进⾏⽐较,可以快速查找到key对应的value。key/value的搜索场景实现的⼆叉树搜索树⽀持修改,但是不⽀持修改key,修改key破坏搜索树性质了,可以修改value。
场景:简单中英互译字典,树的结构中(结点)存储key(英⽂)和vlaue(中⽂),搜索时输⼊英⽂,则同时 查找到了英⽂对应的中⽂。
7.3 代码实现
cpp
namespace keyValue
{
template<class K, class V>
struct BSTNode
{
// pair<K, V> _kv;
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)
{ }
};
template<class K, class V>
class BSTree
{
typedef BSTNode<K, V> Node;
public:
BSTree() = default;
BSTree(const BSTree<K, V>& t)
{
_root = Copy(t._root);
}
BSTree<K, V>& operator=(BSTree<K, V> t)
{
swap(_root, t._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* parent = nullptr;
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
parent = cur;
cur = cur->_right;
}
else if (cur->_key > key)
{
parent = cur;
cur = cur->_left;
}
else
{
return false;
}
}
cur = new Node(key, value);
if (parent->_key < key)
{
parent->_right = cur;
}
else
{
parent->_left = cur;
}
return true;
}
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return cur;
}
}
return nullptr;
}
bool Erase(const K& key)
{
Node* parent = nullptr;
Node* cur = _root;
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)
{
_root = cur->_right;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_right;
else
parent->_right = cur->_right;
}
delete cur;
return true;
}
else if (cur->_right == nullptr)
{
if (parent == nullptr)
{
_root = cur->_left;
}
else
{
if (parent->_left == cur)
parent->_left = cur->_left;
else
parent->_right = cur->_left;
}
delete cur;
return true;
}
else
{
Node* rightMinP = cur;
Node* rightMin = cur->_right;
while (rightMin->_left)
{
rightMinP = rightMin;
rightMin = rightMin->_left;
}
cur->_key = rightMin->_key;
if (rightMinP->_left == rightMin)
rightMinP->_left = rightMin->_right;
else
rightMinP->_right = rightMin->_right;
delete rightMin;
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;
};
}
int main()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
keyValue::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 == NULL)
{
countTree.Insert(str, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
return 0;
}
本期的分享就到这里,如果觉得博主的文章比较对胃口的话,可以点一个小小的关注~
您的三连是我持续更新的动力~