目录
搜索二叉树概念
二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值3.它的左右子树也分别为二叉搜索树
tips:搜索二叉树的中序遍历是升序!
搜索二叉树的查找效率最好是logN(接近满二叉树), 最坏是N(当插入的数据接近有序时)
搜索二叉树模拟实现
搜索二叉树节点结构的定义
template <class K>
struct BSTreeNode
{
K _key;
BSTreeNode<K>* _left;
BSTreeNode<K>* _right;
BSTreeNode(const K& key)
:_left(nullptr)
,_right(nullptr)
,_key(key)
{}
};
搜索二叉树函数声明
template <class K>
class BSTree
{
typedef BSTreeNode<K> Node;
public:
//查找
bool Find(const K& key);
//插入
bool Insert(const K& key);
//删除
bool Erase(const K& key);
//中序遍历
void _InOrder(Node* root);
//解决类外部拿不到_root的问题
void InOrder();
//析构函数
~BSTree();
void Destory(Node*& root);
BSTree() {}; //默认构造,防止编译器报错
//BSTree() = default; //强制生成默认构造函数
//拷贝构造
BSTree(const BSTree<K>& t);
Node* Copy(Node* root);
//赋值重载
BSTree<K>& operator=(BSTree<K> t);
private:
Node* _root = nullptr;
};
非递归实现
查找
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;
}
插入
1.插入的值在搜索二叉树中已经存在,我们直接返回
2.注意保存插入位置的父节点指针,因为还要链接
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; //插入的值在树中存在,直接返回false
}
}
//链接
cur = new Node(key);
if (parent->_key < key) //比父亲大链接到右边
{
parent->_right = cur;
}
else //比父亲小链接到左边
{
parent->_left = cur;
}
return true;
}
删除
删除的节点情况分类
a. 要删除的结点是叶子节点,没有左右孩子b. 要删除的结点只有左孩子结点 或 只有右孩子c. 要删除的结点有左、右孩子结点
![](https://i-blog.csdnimg.cn/blog_migrate/6a086012170d62e901f9d5537c26be3f.png)
![](https://i-blog.csdnimg.cn/blog_migrate/608e406e258a2799126e59ae96870323.png)
![](https://i-blog.csdnimg.cn/blog_migrate/6428526ba95c655b726a3ca2b5656873.png)
![](https://i-blog.csdnimg.cn/blog_migrate/89bd6a5808ca32d501e762212393eb9e.png)
注意:右子树的最左节点不一定是它父亲的左孩子, 因此交换之后仍然要判断右子树的最左节点是父亲的左还是父亲的右
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
{
//1.要删除的节点左为空, 让父亲指向cur的右
if (cur->_left == nullptr)
{
//要删除的节点是根节点
if (cur == _root)
{
_root = cur->_right;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_right;
}
else
{
parent->_right = cur->_right;
}
}
}
//2.要删除的节点右为空, 让父亲指向cur的左
else if (cur->_right == nullptr)
{
//要删除的节点是根节点
if (cur == _root)
{
_root = cur->_left;
}
else
{
if (cur == parent->_left)
{
parent->_left = cur->_left;
}
else
{
parent->_right = cur->_left;
}
}
}
//3.要删除的节点左右都不为空
else
{
Node* parent = cur;
Node* subLeft = cur->_right;
while (subLeft->_left)
{
parent = subLeft;
subLeft = subLeft->_left;
}
swap(cur->_key, subLeft->_key);
if (subLeft == parent->_left)
parent->_left = subLeft->_right;
else
parent->_right = subLeft->_right;
}
return true;
}
}
return false;
}
注意:要删除的节点左右都不为空时, parent开始不能设为nullptr, 正如下图所示图片,如果删的是8, parent为nullptr, while循环进不去,parent仍然为空指针,空指针解引用代码崩溃!而让parent = cur 刚好可以解决下图所示的场景
递归实现
中序遍历
注意,中序遍历递归一定是要有参数的,而如果有了root参数,类外面无法拿到_root, 因为是私有的,所以再提供一个函数不带参的,然后去调用带参函数,此时C++代码如下写法:
//中序遍历
void _InOrder(Node* root)
{
if (root == nullptr)
return;
_InOrder(root->_left);
cout << root->_key << " ";
_InOrder(root->_right);
}
//解决类外部拿不到_root的问题
void InOrder()
{
_InOrder(_root);
cout << endl;
}
查找
key比root->key大, 转化成去右子树查找,key比root->key小, 转化成去左子树查找
bool FindR(const K& key)
{
return _FindR(_root, key);
}
bool _FindR(Node* root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _FindR(root->_right, key);
}
else if(root->_key > key)
{
return _FindR(root->_left, key);
}
else
{
return true;
}
}
插入
key比root->key大, 转化成去右子树插入,key比root->key小, 转化成去左子树插入
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;
}
if (root->_key < key)
{
return _InsertR(root->_right, key);
}
else if (root->_key > key)
{
return _InsertR(root->_left, key);
}
else
{
return false;
}
}
这里重点体会参数root给引用的妙处,当最后的root是nullptr时,同时也是父节点的左指针或右指针的别名,所以root = new Node(key) 直接就把新开辟的节点和父节点链接起来了!!!
删除
要删除的key比root->key大, 转化成去右子树删除,key比root->key小, 转化成去左子树删除
bool EraseR(const K& key)
{
return _EraseR(_root, key);
}
bool _EraseR(Node*& root, const K& key)
{
if (root == nullptr)
{
return false;
}
if (root->_key < key)
{
return _EraseR(root->_right, key);
}
else if (root->_key > key)
{
return _EraseR(root->_left, key);
}
else
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
else if (root->_right == nullptr)
{
if (root->_left == nullptr)
{
Node* del = root;
root = root->_right;
delete del;
return true;
}
}
else
{
Node* subLeft = root->_right;
while (subLeft->_left)
{
subLeft = subLeft->_left;
}
swap(root->_key, subLeft->_key);
//转换成在子树递归删除
return _EraseR(root->_right, key);
}
}
}
当key和root->key相等时,仍然是分情况讨论
当删除的节点左为空或者右为空时,我们仍然可以使用引用巧妙解决问题!
当删除的节点左为空或者右为空的节点是根节点,也没有问题
当删除的节点左右都不为空,我们可以转化成递归子树去删除!不能转化成去递归搜索二叉树, 因为替换法之后就不一定是颗搜索二叉树了,而如图所示的子树仍然是搜索二叉树
当删除的节点左右都不为空,要删除的节点是根时也没有问题
析构函数
~BSTree()
{
Destory(_root);
}
void Destory(Node*& root)
{
if (root == nullptr)
return;
Destory(root->_left);
Destory(root->_right);
delete root;
}
拷贝构造函数
BSTree() {}; //默认构造,防止编译器报错
//BSTree() = default; //强制生成默认构造函数
//拷贝构造
BSTree(const BSTree<K>& t)
{
_root = Copy(t._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;
}
赋值重载
BSTree<K>& operator=(BSTree<K> t)
{
swap(_root, t._root);
return *this;
}
搜索二叉树应用场景
1.key在不在的问题
比如门禁系统本质就是key在不在的问题
2.key-value的问题
namespace kv
{
template <class K, class V>
struct BSTreeNode
{
BSTreeNode<K, V>* _left;
BSTreeNode<K, V>* _right;
K _key;
V _value;
BSTreeNode(const K& key, const V& value)
:_left(nullptr)
, _right(nullptr)
, _key(key)
,_value(value)
{}
};
template <class K, class V>
class BSTree
{
typedef BSTreeNode<K, V> Node;
public:
//插入
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)
{
parent = cur;
if (cur->_key < key)
{
cur = cur->_right;
}
else if (cur->_key > key)
{
cur = cur->_left;
}
else
{
return false; //插入的值在树中存在,直接返回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;
}
//中序遍历
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()
{
kv::BSTree<string, string> dict;
dict.Insert("sort", "排序");
dict.Insert("left", "左边");
dict.Insert("right", "右边");
dict.Insert("insert", "插入");
string str;
while (cin >> str)
{
kv::BSTreeNode<string, string>* ret = dict.Find(str);
if (ret)
{
cout << ret->_value << endl;
}
else
{
cout << "字典中不存在" << endl;
}
}
}
key-value模型也可以用来统计key的个数
int main()
{
string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
kv::BSTree<string, int> countTree;
for (auto& e : arr)
{
kv::BSTreeNode<string, int>* ret = countTree.Find(e);
if (ret == nullptr)
{
countTree.Insert(e, 1);
}
else
{
ret->_value++;
}
}
countTree.InOrder();
}